@omnicoreos/planka-mcp 0.2.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/CHANGELOG.md +86 -0
- package/CONTRIBUTING.md +37 -0
- package/CREDITS.md +39 -0
- package/LICENSE +22 -0
- package/README.es.md +213 -0
- package/README.md +213 -0
- package/dist/client.d.ts +96 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +281 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +85 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +226 -0
- package/dist/errors.js.map +1 -0
- package/dist/identity.generated.d.ts +3 -0
- package/dist/identity.generated.d.ts.map +1 -0
- package/dist/identity.generated.js +4 -0
- package/dist/identity.generated.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +93 -0
- package/dist/index.js.map +1 -0
- package/dist/operations/attachments.d.ts +34 -0
- package/dist/operations/attachments.d.ts.map +1 -0
- package/dist/operations/attachments.js +89 -0
- package/dist/operations/attachments.js.map +1 -0
- package/dist/operations/board-id.d.ts +9 -0
- package/dist/operations/board-id.d.ts.map +1 -0
- package/dist/operations/board-id.js +86 -0
- package/dist/operations/board-id.js.map +1 -0
- package/dist/operations/boards.d.ts +143 -0
- package/dist/operations/boards.d.ts.map +1 -0
- package/dist/operations/boards.js +391 -0
- package/dist/operations/boards.js.map +1 -0
- package/dist/operations/cards.d.ts +36 -0
- package/dist/operations/cards.d.ts.map +1 -0
- package/dist/operations/cards.js +81 -0
- package/dist/operations/cards.js.map +1 -0
- package/dist/operations/comments.d.ts +28 -0
- package/dist/operations/comments.d.ts.map +1 -0
- package/dist/operations/comments.js +53 -0
- package/dist/operations/comments.js.map +1 -0
- package/dist/operations/labels.d.ts +60 -0
- package/dist/operations/labels.d.ts.map +1 -0
- package/dist/operations/labels.js +146 -0
- package/dist/operations/labels.js.map +1 -0
- package/dist/operations/lists.d.ts +15 -0
- package/dist/operations/lists.d.ts.map +1 -0
- package/dist/operations/lists.js +40 -0
- package/dist/operations/lists.js.map +1 -0
- package/dist/operations/projects.d.ts +24 -0
- package/dist/operations/projects.d.ts.map +1 -0
- package/dist/operations/projects.js +61 -0
- package/dist/operations/projects.js.map +1 -0
- package/dist/operations/tasks.d.ts +30 -0
- package/dist/operations/tasks.d.ts.map +1 -0
- package/dist/operations/tasks.js +111 -0
- package/dist/operations/tasks.js.map +1 -0
- package/dist/schemas/entities.d.ts +389 -0
- package/dist/schemas/entities.d.ts.map +1 -0
- package/dist/schemas/entities.js +202 -0
- package/dist/schemas/entities.js.map +1 -0
- package/dist/schemas/requests.d.ts +539 -0
- package/dist/schemas/requests.d.ts.map +1 -0
- package/dist/schemas/requests.js +187 -0
- package/dist/schemas/requests.js.map +1 -0
- package/dist/schemas/responses.d.ts +2626 -0
- package/dist/schemas/responses.d.ts.map +1 -0
- package/dist/schemas/responses.js +75 -0
- package/dist/schemas/responses.js.map +1 -0
- package/dist/tools/attachments.d.ts +270 -0
- package/dist/tools/attachments.d.ts.map +1 -0
- package/dist/tools/attachments.js +247 -0
- package/dist/tools/attachments.js.map +1 -0
- package/dist/tools/cards.d.ts +401 -0
- package/dist/tools/cards.d.ts.map +1 -0
- package/dist/tools/cards.js +368 -0
- package/dist/tools/cards.js.map +1 -0
- package/dist/tools/comments.d.ts +217 -0
- package/dist/tools/comments.d.ts.map +1 -0
- package/dist/tools/comments.js +208 -0
- package/dist/tools/comments.js.map +1 -0
- package/dist/tools/index.d.ts +1293 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +62 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/labels.d.ts +214 -0
- package/dist/tools/labels.d.ts.map +1 -0
- package/dist/tools/labels.js +302 -0
- package/dist/tools/labels.js.map +1 -0
- package/dist/tools/lists.d.ts +117 -0
- package/dist/tools/lists.d.ts.map +1 -0
- package/dist/tools/lists.js +178 -0
- package/dist/tools/lists.js.map +1 -0
- package/dist/tools/navigation.d.ts +106 -0
- package/dist/tools/navigation.d.ts.map +1 -0
- package/dist/tools/navigation.js +151 -0
- package/dist/tools/navigation.js.map +1 -0
- package/dist/tools/queries.d.ts +380 -0
- package/dist/tools/queries.d.ts.map +1 -0
- package/dist/tools/queries.js +256 -0
- package/dist/tools/queries.js.map +1 -0
- package/dist/tools/tasks.d.ts +223 -0
- package/dist/tools/tasks.d.ts.map +1 -0
- package/dist/tools/tasks.js +169 -0
- package/dist/tools/tasks.js.map +1 -0
- package/docs/planka-2x-gotchas.md +192 -0
- package/docs/tools.md +659 -0
- package/docs/troubleshooting.md +138 -0
- package/package.json +71 -0
- package/project.identity.json +7 -0
- package/scripts/bootstrap-board.mjs +160 -0
- package/scripts/lib/planka-api.mjs +244 -0
- package/scripts/lib/workflow-template.mjs +26 -0
- package/scripts/setup.mjs +236 -0
- package/scripts/setup.sh +48 -0
- package/scripts/sync-identity.mjs +57 -0
- package/server.json +43 -0
- package/tests/smoke/planka-smoke.mjs +920 -0
- package/workflow/README.md +232 -0
- package/workflow/board-template.md +115 -0
- package/workflow/skills/planka-close-card/SKILL.md +81 -0
- package/workflow/skills/planka-orchestrator/SKILL.md +126 -0
- package/workflow/worktrees/README.md +104 -0
- package/workflow/worktrees/wt.conf.example +24 -0
- package/workflow/worktrees/wt.sh +345 -0
package/docs/tools.md
ADDED
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
# Tools reference
|
|
2
|
+
|
|
3
|
+
The server exposes exactly 24 tools. Tool results are MCP text content containing
|
|
4
|
+
formatted JSON. On an expected Planka error, the result has `isError: true` and a
|
|
5
|
+
human-readable message instead of a false success.
|
|
6
|
+
|
|
7
|
+
## Before your first call
|
|
8
|
+
|
|
9
|
+
- All Planka IDs are strings, even when they contain only digits.
|
|
10
|
+
- Discover IDs with `planka_get_structure`, `planka_list_lists`, or
|
|
11
|
+
`planka_get_board`.
|
|
12
|
+
- Reading a whole board is the expensive call. When you only need one column,
|
|
13
|
+
one label, or the shape of the board, use a [scoped read](#scoped-reads).
|
|
14
|
+
- Dates use ISO 8601, for example `2026-09-01T17:00:00.000Z`.
|
|
15
|
+
- Omit optional fields you do not want to change. Use `null` only where the schema
|
|
16
|
+
explicitly allows it.
|
|
17
|
+
- Destructive tools really delete data. Ask for confirmation in your agent prompt
|
|
18
|
+
when that matters.
|
|
19
|
+
|
|
20
|
+
## Navigation
|
|
21
|
+
|
|
22
|
+
### `planka_get_structure`
|
|
23
|
+
|
|
24
|
+
Lists the project → board → list hierarchy visible to the agent user. Pass an
|
|
25
|
+
empty object to inspect everything:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or restrict it to one project:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"projectId": "1234567890123456789"
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### `planka_get_board`
|
|
40
|
+
|
|
41
|
+
Reads one board with its labels, lists, cards, and optional checklist counts.
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"boardId": "2234567890123456789",
|
|
46
|
+
"includeTaskCounts": true
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Set `includeTaskCounts` to `false` for a smaller response. Labels include their
|
|
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` |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `planka_get_board` | 44,449 | baseline |
|
|
66
|
+
| `planka_list_lists` | 1,147 | 39x smaller |
|
|
67
|
+
| `planka_board_summary` | 2,737 | 16x smaller |
|
|
68
|
+
| `planka_find_cards` filtered by label | 2,421 | 18x smaller |
|
|
69
|
+
| `planka_find_cards` filtered by text | 795 | 56x smaller |
|
|
70
|
+
|
|
71
|
+
The gap widens as the board grows, because only `planka_get_board` grows with
|
|
72
|
+
the number of cards.
|
|
73
|
+
|
|
74
|
+
Which one to reach for:
|
|
75
|
+
|
|
76
|
+
| Question | Tool |
|
|
77
|
+
|---|---|
|
|
78
|
+
| What columns does this board have, and how full is each one? | `planka_list_lists` |
|
|
79
|
+
| What is on this board and what should I pick up first? | `planka_board_summary` |
|
|
80
|
+
| What is in this one column? | `planka_list_cards` |
|
|
81
|
+
| Which cards carry this label, or mention this text? | `planka_find_cards` |
|
|
82
|
+
| I need every card with every field, in one object | `planka_get_board` |
|
|
83
|
+
|
|
84
|
+
All four report how much they clipped (`total`, `returned`, `hasMore`,
|
|
85
|
+
`nextOffset`), so a partial answer is never mistaken for a complete one.
|
|
86
|
+
|
|
87
|
+
### `planka_list_lists`
|
|
88
|
+
|
|
89
|
+
Lists the columns of a board with the number of cards in each one, and no cards
|
|
90
|
+
at all. Use it to see the shape of a board before deciding which column to open.
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"boardId": "2234567890123456789"
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`planka_get_structure` returns the same column names but not the counts.
|
|
99
|
+
Archive and trash lists are excluded, because they are not board columns.
|
|
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`.
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"boardId": "2234567890123456789",
|
|
111
|
+
"cardsFrom": [
|
|
112
|
+
"blocked"
|
|
113
|
+
],
|
|
114
|
+
"highlightLabel": "ready",
|
|
115
|
+
"limit": 50
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Every field except `boardId` is optional, and the defaults are opinionated:
|
|
120
|
+
|
|
121
|
+
| Field | Default | What the default does |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| `cardsFrom` | `["decision", "decide", "blocked", "bloquead"]` | Picks the columns whose names contain one of these fragments |
|
|
124
|
+
| `highlightLabel` | `"decidido"` | Marks cards carrying a label with that exact name |
|
|
125
|
+
| `limit` | `50` | Caps returned cards; maximum `200` |
|
|
126
|
+
|
|
127
|
+
Those defaults come from the board layout this server was written against, which
|
|
128
|
+
is why one of them is Spanish. They are matching hints, not a requirement: pass
|
|
129
|
+
your own values and the tool behaves exactly the same on an English board.
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"boardId": "2234567890123456789",
|
|
134
|
+
"cardsFrom": [
|
|
135
|
+
"blocked",
|
|
136
|
+
"needs review"
|
|
137
|
+
],
|
|
138
|
+
"highlightLabel": "approved"
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`cardsFrom` entries match either an exact list ID or a fragment of a column
|
|
143
|
+
name, ignoring case and accents. `highlightLabel` must match a label name
|
|
144
|
+
exactly, again ignoring case and accents. The response echoes the columns it
|
|
145
|
+
actually selected in `cardsFrom` and the label it used in `highlightLabel`, so a
|
|
146
|
+
hint that matched nothing is visible instead of silent.
|
|
147
|
+
|
|
148
|
+
Card payload is deliberately minimal: `id`, `name`, `list`, `labels`. Follow up
|
|
149
|
+
with `planka_get_card` for one card rather than `planka_get_board` for all of
|
|
150
|
+
them.
|
|
151
|
+
|
|
152
|
+
### `planka_list_cards`
|
|
153
|
+
|
|
154
|
+
Reads the cards of one column: `id`, `name`, labels, checklist counts, and due
|
|
155
|
+
date. Descriptions are off by default because they are the expensive field.
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"listId": "3234567890123456789",
|
|
160
|
+
"limit": 25,
|
|
161
|
+
"offset": 0
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Filter the column by label name, or ask for description previews:
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"listId": "3234567890123456789",
|
|
170
|
+
"labelFilter": "ready",
|
|
171
|
+
"withDescription": true,
|
|
172
|
+
"limit": 50
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
| Field | Default | Notes |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| `withLabels` | `true` | Resolving label names costs one extra board read |
|
|
179
|
+
| `withDescription` | `false` | Adds a 200-character preview per card |
|
|
180
|
+
| `labelFilter` | none | Exact label name, case- and accent-insensitive; implies `withLabels` |
|
|
181
|
+
| `limit` | `25` | Maximum `200` |
|
|
182
|
+
| `offset` | `0` | Pass the previous response's `nextOffset` to page |
|
|
183
|
+
|
|
184
|
+
`limit` and `offset` clip the response, not the request: Planka's own list
|
|
185
|
+
endpoint accepts neither, so the server resolves the whole column through
|
|
186
|
+
Planka's cursor and pages the output. That is what makes `total` honest. If
|
|
187
|
+
pagination stops before the column is exhausted, the response carries
|
|
188
|
+
`truncated: true` and a note saying so.
|
|
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
|
+
```
|
|
205
|
+
|
|
206
|
+
Check whether a card about something already exists, before creating a
|
|
207
|
+
duplicate:
|
|
208
|
+
|
|
209
|
+
```json
|
|
210
|
+
{
|
|
211
|
+
"boardId": "2234567890123456789",
|
|
212
|
+
"query": "export timeout",
|
|
213
|
+
"withDescription": true
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Filters combine, and at least one of `labelName`, `query`, or `listId` is
|
|
218
|
+
required; an unfiltered search would just be a slower `planka_get_board`.
|
|
219
|
+
|
|
220
|
+
| Field | Default | Notes |
|
|
221
|
+
|---|---|---|
|
|
222
|
+
| `labelName` | none | Exact label name, case- and accent-insensitive |
|
|
223
|
+
| `query` | none | Substring matched against card name **and** description, case- and accent-insensitive |
|
|
224
|
+
| `listId` | none | Restricts the search to one column |
|
|
225
|
+
| `withDescription` | `false` | Adds a 200-character preview per match |
|
|
226
|
+
| `limit` | `25` | Maximum `200` |
|
|
227
|
+
| `offset` | `0` | Pass the previous response's `nextOffset` to page |
|
|
228
|
+
|
|
229
|
+
If the board has no label with the requested name, the response still succeeds
|
|
230
|
+
and carries a `warning` naming the labels that do exist. The same happens for a
|
|
231
|
+
`listId` that is not on the board.
|
|
232
|
+
|
|
233
|
+
## Cards
|
|
234
|
+
|
|
235
|
+
### `planka_create_card`
|
|
236
|
+
|
|
237
|
+
Creates a card. Tasks and labels are optional follow-up writes performed by the
|
|
238
|
+
same tool.
|
|
239
|
+
|
|
240
|
+
```json
|
|
241
|
+
{
|
|
242
|
+
"listId": "3234567890123456789",
|
|
243
|
+
"name": "Verify the export flow",
|
|
244
|
+
"description": "Reproduce the timeout and record the expected result.",
|
|
245
|
+
"tasks": [
|
|
246
|
+
"Create a large sample",
|
|
247
|
+
"Run the export",
|
|
248
|
+
"Record the duration"
|
|
249
|
+
],
|
|
250
|
+
"dueDate": "2026-09-01T17:00:00.000Z",
|
|
251
|
+
"labelIds": [
|
|
252
|
+
"4234567890123456789"
|
|
253
|
+
]
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Only `listId` and `name` are required. The result reports `tasksCreated`,
|
|
258
|
+
`labelsAttached`, and any individual `labelErrors`.
|
|
259
|
+
|
|
260
|
+
### `planka_get_card`
|
|
261
|
+
|
|
262
|
+
Reads full card details, including tasks, comments, labels, and attachments.
|
|
263
|
+
|
|
264
|
+
```json
|
|
265
|
+
{
|
|
266
|
+
"cardId": "5234567890123456789"
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### `planka_update_card`
|
|
271
|
+
|
|
272
|
+
Updates only the fields present in the request.
|
|
273
|
+
|
|
274
|
+
```json
|
|
275
|
+
{
|
|
276
|
+
"cardId": "5234567890123456789",
|
|
277
|
+
"name": "Verify and document the export flow",
|
|
278
|
+
"description": "Timeout reproduced. Add the accepted duration to the docs.",
|
|
279
|
+
"dueDate": null,
|
|
280
|
+
"isCompleted": false
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Use `null` to clear `description` or `dueDate`.
|
|
285
|
+
|
|
286
|
+
### `planka_move_card`
|
|
287
|
+
|
|
288
|
+
Moves a card to a list and optionally assigns its numeric position.
|
|
289
|
+
|
|
290
|
+
```json
|
|
291
|
+
{
|
|
292
|
+
"cardId": "5234567890123456789",
|
|
293
|
+
"listId": "6234567890123456789",
|
|
294
|
+
"position": 65536
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
If `position` is omitted, Planka decides the final position.
|
|
299
|
+
|
|
300
|
+
### `planka_delete_card`
|
|
301
|
+
|
|
302
|
+
Permanently deletes a card.
|
|
303
|
+
|
|
304
|
+
```json
|
|
305
|
+
{
|
|
306
|
+
"cardId": "5234567890123456789"
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Checklist tasks
|
|
311
|
+
|
|
312
|
+
### `planka_create_tasks`
|
|
313
|
+
|
|
314
|
+
Adds one or more checklist tasks to a card.
|
|
315
|
+
|
|
316
|
+
```json
|
|
317
|
+
{
|
|
318
|
+
"cardId": "5234567890123456789",
|
|
319
|
+
"tasks": [
|
|
320
|
+
"Add a regression test",
|
|
321
|
+
"Update the operator notes"
|
|
322
|
+
]
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### `planka_update_task`
|
|
327
|
+
|
|
328
|
+
Renames a task, changes completion, or does both.
|
|
329
|
+
|
|
330
|
+
```json
|
|
331
|
+
{
|
|
332
|
+
"taskId": "7234567890123456789",
|
|
333
|
+
"name": "Add and run a regression test",
|
|
334
|
+
"isCompleted": true
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### `planka_delete_task`
|
|
339
|
+
|
|
340
|
+
Deletes one checklist task.
|
|
341
|
+
|
|
342
|
+
```json
|
|
343
|
+
{
|
|
344
|
+
"taskId": "7234567890123456789"
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Labels
|
|
349
|
+
|
|
350
|
+
### `planka_manage_labels`
|
|
351
|
+
|
|
352
|
+
Creates, updates, or deletes a board label. The `action` determines which IDs
|
|
353
|
+
are required.
|
|
354
|
+
|
|
355
|
+
Create:
|
|
356
|
+
|
|
357
|
+
```json
|
|
358
|
+
{
|
|
359
|
+
"action": "create",
|
|
360
|
+
"boardId": "2234567890123456789",
|
|
361
|
+
"name": "needs-decision",
|
|
362
|
+
"color": "berry-red"
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Update:
|
|
367
|
+
|
|
368
|
+
```json
|
|
369
|
+
{
|
|
370
|
+
"action": "update",
|
|
371
|
+
"labelId": "4234567890123456789",
|
|
372
|
+
"name": "decision-needed",
|
|
373
|
+
"color": "pumpkin-orange"
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Delete:
|
|
378
|
+
|
|
379
|
+
```json
|
|
380
|
+
{
|
|
381
|
+
"action": "delete",
|
|
382
|
+
"labelId": "4234567890123456789"
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Writes validate colors against the Planka 2.x palette. Run
|
|
387
|
+
`planka_get_board` to see colors already in use. Unknown colors are accepted on
|
|
388
|
+
reads so one new upstream color cannot break the whole board response.
|
|
389
|
+
|
|
390
|
+
### `planka_set_card_labels`
|
|
391
|
+
|
|
392
|
+
Adds and removes labels, then re-reads the card and reports the verified final
|
|
393
|
+
state. `labelIds` is a supported alias for `addLabelIds`; if both are provided,
|
|
394
|
+
their values are merged.
|
|
395
|
+
|
|
396
|
+
```json
|
|
397
|
+
{
|
|
398
|
+
"cardId": "5234567890123456789",
|
|
399
|
+
"addLabelIds": [
|
|
400
|
+
"4234567890123456789"
|
|
401
|
+
],
|
|
402
|
+
"labelIds": [
|
|
403
|
+
"8234567890123456789"
|
|
404
|
+
],
|
|
405
|
+
"removeLabelIds": [
|
|
406
|
+
"9234567890123456789"
|
|
407
|
+
]
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
The response distinguishes requested, added, removed, already present, already
|
|
412
|
+
absent, failed, and final label IDs. This makes silent no-op writes visible.
|
|
413
|
+
|
|
414
|
+
## Comments
|
|
415
|
+
|
|
416
|
+
### `planka_add_comment`
|
|
417
|
+
|
|
418
|
+
Adds a Markdown-capable comment through Planka 2.x's dedicated card action
|
|
419
|
+
endpoint.
|
|
420
|
+
|
|
421
|
+
```json
|
|
422
|
+
{
|
|
423
|
+
"cardId": "5234567890123456789",
|
|
424
|
+
"text": "Verification complete: unit tests and the manual export both passed."
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### `planka_get_comments`
|
|
429
|
+
|
|
430
|
+
Reads the dedicated comment collection for one card.
|
|
431
|
+
|
|
432
|
+
```json
|
|
433
|
+
{
|
|
434
|
+
"cardId": "5234567890123456789"
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
This does not rely on `included.comments`, which is empty in affected Planka 2.x
|
|
439
|
+
responses even when the card has comments.
|
|
440
|
+
|
|
441
|
+
### `planka_manage_comment`
|
|
442
|
+
|
|
443
|
+
Edits or deletes an existing comment. Get comment IDs from
|
|
444
|
+
`planka_get_comments`.
|
|
445
|
+
|
|
446
|
+
Update:
|
|
447
|
+
|
|
448
|
+
```json
|
|
449
|
+
{
|
|
450
|
+
"action": "update",
|
|
451
|
+
"commentId": "1034567890123456789",
|
|
452
|
+
"text": "Verification complete: unit tests and the manual export both passed."
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
Delete:
|
|
457
|
+
|
|
458
|
+
```json
|
|
459
|
+
{
|
|
460
|
+
"action": "delete",
|
|
461
|
+
"commentId": "1034567890123456789"
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
`text` is required for `update` and ignored for `delete`. Planka only lets the
|
|
466
|
+
authenticated user modify comments that user wrote, so an agent can correct its
|
|
467
|
+
own notes but not someone else's.
|
|
468
|
+
|
|
469
|
+
## Lists
|
|
470
|
+
|
|
471
|
+
### `planka_manage_lists`
|
|
472
|
+
|
|
473
|
+
Creates, updates, or deletes a board list. Creation sends an explicit Planka 2.x
|
|
474
|
+
list `type`; it defaults to `active`.
|
|
475
|
+
|
|
476
|
+
Create:
|
|
477
|
+
|
|
478
|
+
```json
|
|
479
|
+
{
|
|
480
|
+
"action": "create",
|
|
481
|
+
"boardId": "2234567890123456789",
|
|
482
|
+
"name": "Pending",
|
|
483
|
+
"position": 65536,
|
|
484
|
+
"type": "active"
|
|
485
|
+
}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
Update:
|
|
489
|
+
|
|
490
|
+
```json
|
|
491
|
+
{
|
|
492
|
+
"action": "update",
|
|
493
|
+
"listId": "3234567890123456789",
|
|
494
|
+
"name": "Ready",
|
|
495
|
+
"position": 131072,
|
|
496
|
+
"type": "active"
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
Delete:
|
|
501
|
+
|
|
502
|
+
```json
|
|
503
|
+
{
|
|
504
|
+
"action": "delete",
|
|
505
|
+
"listId": "3234567890123456789"
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
Only `active` and `closed` are accepted for list writes. Internal Planka list
|
|
510
|
+
types such as archive and trash remain readable.
|
|
511
|
+
|
|
512
|
+
## Attachments
|
|
513
|
+
|
|
514
|
+
Attachments carry the evidence a card needs to be understood later: a
|
|
515
|
+
screenshot of the bug, a before/after pair, a failing console. Prefer a code
|
|
516
|
+
block in a comment for stack traces, diffs, and logs; an image of text is
|
|
517
|
+
harder to read and impossible to search.
|
|
518
|
+
|
|
519
|
+
### `planka_add_attachment`
|
|
520
|
+
|
|
521
|
+
Uploads a local file to a card, then re-reads the card's attachments to confirm
|
|
522
|
+
it landed. The response carries `verified`, and the result is flagged as an
|
|
523
|
+
error when the re-read does not find the upload.
|
|
524
|
+
|
|
525
|
+
```json
|
|
526
|
+
{
|
|
527
|
+
"cardId": "5234567890123456789",
|
|
528
|
+
"filePath": "/home/user/screenshots/export-timeout.png",
|
|
529
|
+
"name": "before-export-timeout.png"
|
|
530
|
+
}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
`filePath` must be an absolute path on the machine running the MCP server.
|
|
534
|
+
`name` is optional and defaults to the file's basename; descriptive names such
|
|
535
|
+
as `before-...`, `after-...`, or `console-error-...` are worth the extra field.
|
|
536
|
+
The content type is derived from the extension, with `application/octet-stream`
|
|
537
|
+
as the fallback. A missing file returns `File not found: <path>` instead of an
|
|
538
|
+
opaque failure.
|
|
539
|
+
|
|
540
|
+
### `planka_get_attachments`
|
|
541
|
+
|
|
542
|
+
Lists what a card holds: `id`, `name`, `type`, `mimeType`, `size`, download
|
|
543
|
+
`url`, and `createdAt`.
|
|
544
|
+
|
|
545
|
+
```json
|
|
546
|
+
{
|
|
547
|
+
"cardId": "5234567890123456789"
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### `planka_view_attachment`
|
|
552
|
+
|
|
553
|
+
Returns an attachment's content rather than its metadata.
|
|
554
|
+
|
|
555
|
+
```json
|
|
556
|
+
{
|
|
557
|
+
"cardId": "5234567890123456789",
|
|
558
|
+
"attachmentId": "1134567890123456789"
|
|
559
|
+
}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
`attachmentId` is optional when the card has exactly one attachment. With more
|
|
563
|
+
than one and no ID, the tool refuses and lists the available IDs and names
|
|
564
|
+
instead of guessing.
|
|
565
|
+
|
|
566
|
+
What comes back depends on the content type:
|
|
567
|
+
|
|
568
|
+
| Content type | Result |
|
|
569
|
+
|---|---|
|
|
570
|
+
| `image/*`, except `image/svg+xml` | MCP image content, so a model with vision actually sees it. Limited to 3 MB; larger images return the download URL instead |
|
|
571
|
+
| `text/*`, `application/json`, `image/svg+xml` | Inline text, clipped at 50 KB and marked `[truncated to 50KB]` when clipped |
|
|
572
|
+
| Anything else | Metadata and the download URL, with a note that it cannot be rendered inline |
|
|
573
|
+
|
|
574
|
+
### `planka_delete_attachment`
|
|
575
|
+
|
|
576
|
+
Permanently deletes one attachment. It is addressed by attachment ID alone, not
|
|
577
|
+
by card.
|
|
578
|
+
|
|
579
|
+
```json
|
|
580
|
+
{
|
|
581
|
+
"attachmentId": "1134567890123456789"
|
|
582
|
+
}
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
## When arguments are wrong
|
|
586
|
+
|
|
587
|
+
Invalid input is reported as an error result naming the tool, the field, what
|
|
588
|
+
the schema expected, and what actually arrived. Three cases are distinguished
|
|
589
|
+
on purpose, because the right correction differs.
|
|
590
|
+
|
|
591
|
+
A required field that never arrived is caught before the handler runs:
|
|
592
|
+
|
|
593
|
+
```text
|
|
594
|
+
Invalid arguments for planka_create_tasks: missing required field(s): tasks.
|
|
595
|
+
- tasks (array) — Task names to create
|
|
596
|
+
Required fields for planka_create_tasks: cardId, tasks
|
|
597
|
+
Arguments received: {"cardId":"5234567890123456789"}
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
A field that arrived with the wrong shape or value reports the received value
|
|
601
|
+
alongside the field's declared type and description:
|
|
602
|
+
|
|
603
|
+
```text
|
|
604
|
+
Invalid arguments for planka_find_cards:
|
|
605
|
+
- (whole arguments object): give at least one filter: labelName, query or listId (an unfiltered search would return the whole board) (received: {"boardId":"2234567890123456789"})
|
|
606
|
+
Required fields for planka_find_cards: boardId
|
|
607
|
+
Arguments received: {"boardId":"2234567890123456789"}
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
A response from Planka that does not match the expected shape is labelled as
|
|
611
|
+
such, so it is not mistaken for a bad argument:
|
|
612
|
+
|
|
613
|
+
```text
|
|
614
|
+
PLANKA returned an unexpected response shape while running planka_get_board.
|
|
615
|
+
This is an API/server mismatch, not a problem with the arguments:
|
|
616
|
+
- item.name: Expected string, received null
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
Long values are clipped in these messages. An unknown tool name is answered with
|
|
620
|
+
the list of tools the server actually exposes.
|
|
621
|
+
|
|
622
|
+
## Raw verification
|
|
623
|
+
|
|
624
|
+
Unit tests validate schemas, request construction, and error formatting without a
|
|
625
|
+
network: run them with `npm test`.
|
|
626
|
+
|
|
627
|
+
The opt-in smoke test is the other half. It starts the built MCP server over stdio,
|
|
628
|
+
drives all 24 tools through real MCP calls, and independently queries the raw
|
|
629
|
+
Planka HTTP API after every mutation, so a tool that reports success on something
|
|
630
|
+
that never landed is caught here. It runs 64 named checks.
|
|
631
|
+
|
|
632
|
+
```bash
|
|
633
|
+
export PLANKA_BASE_URL="https://planka.example.com"
|
|
634
|
+
export PLANKA_AGENT_EMAIL="agent@example.com"
|
|
635
|
+
export PLANKA_AGENT_PASSWORD="<YOUR_PASSWORD>"
|
|
636
|
+
export PLANKA_SMOKE_BOARD_ID="2234567890123456789"
|
|
637
|
+
npm run test:smoke
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
`npm run test:smoke` builds first, so it cannot silently test a stale `dist/`.
|
|
641
|
+
|
|
642
|
+
| Variable | Required | What it does |
|
|
643
|
+
|---|---|---|
|
|
644
|
+
| `PLANKA_BASE_URL` | yes | Planka origin, the same one the MCP client uses |
|
|
645
|
+
| `PLANKA_AGENT_EMAIL` | yes | Agent account email or username |
|
|
646
|
+
| `PLANKA_AGENT_PASSWORD` | yes | Agent account password |
|
|
647
|
+
| `PLANKA_SMOKE_BOARD_ID` | yes | Board to run against. Use a writable, non-critical board |
|
|
648
|
+
| `PLANKA_SMOKE_LIST_ID` | no | Column where the scratch card is created. Without it, the test picks a scratch-looking column, falling back to the first one |
|
|
649
|
+
| `PLANKA_SMOKE_FAIL_AFTER` | no | Injects a failure after check `<n>`, to prove that cleanup still runs. Use it to verify the safety net, not the tools |
|
|
650
|
+
| `PLANKA_MCP_ENTRY` | no | Server entry point. Defaults to `dist/index.js` |
|
|
651
|
+
| `VERBOSE` | no | Set to `1` to print each check's payload |
|
|
652
|
+
|
|
653
|
+
Everything the test creates is prefixed and deleted in a `finally`, and on
|
|
654
|
+
`SIGINT`/`SIGTERM` too. Cleanup goes over raw HTTP on purpose: when a signal kills
|
|
655
|
+
the test process, the MCP server child dies with it, so the safety net cannot
|
|
656
|
+
depend on the thing under test.
|
|
657
|
+
|
|
658
|
+
Exit codes: `0` all checks passed, `1` a check failed, `2` a configuration
|
|
659
|
+
problem.
|