@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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,516 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented here.
|
|
4
4
|
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.3.0] - 2026-09-03
|
|
8
|
+
|
|
9
|
+
### Highlights
|
|
10
|
+
|
|
11
|
+
- **Every write is verified.** The server writes, re-reads the entity and
|
|
12
|
+
reports what the re-read says — never what was asked for. `success` mirrors
|
|
13
|
+
`verified` and nothing else: "I could not check" now answers `success: false`
|
|
14
|
+
with `isError` and names the parameter that would make the check possible.
|
|
15
|
+
- **API key authentication and a real access policy.** `PLANKA_API_KEY` replaces
|
|
16
|
+
the login (no token refresh, no sign-in rate limit), and seven environment
|
|
17
|
+
variables scope what the server may see and touch: default board, board and
|
|
18
|
+
project allowlists, read-only mode, disabled tools, protected columns, hidden
|
|
19
|
+
deprecated tools.
|
|
20
|
+
- **`instructions` and annotations on the handshake.** The shared guidance is
|
|
21
|
+
stated once instead of 41 times, every tool publishes a title and all four MCP
|
|
22
|
+
behaviour hints explicitly, and the eleven tools that lose data carry Claude
|
|
23
|
+
Code's `requiresUserInteraction` prompt.
|
|
24
|
+
- **Reads return digests, and say what they clipped.** Descriptions are off
|
|
25
|
+
unless asked for, results are capped, and every scoped read reports
|
|
26
|
+
`total`/`returned`/`hasMore`/`nextOffset`, so a clipped answer is never
|
|
27
|
+
mistaken for a complete one. `planka_get_board` went 56 k → 17 k characters.
|
|
28
|
+
- **Eight new tools**: `planka_board_summary`, `planka_find_cards`,
|
|
29
|
+
`planka_get_activity`, `planka_list_users`, `planka_set_card_members`,
|
|
30
|
+
`planka_duplicate_card`, `planka_archive_card`, `planka_move_list_cards`.
|
|
31
|
+
- **The three `manage_*` tools are split into one tool per action** (32 → 41
|
|
32
|
+
tools) so `required` finally means something, with the old names kept as
|
|
33
|
+
deprecated aliases that route to the replacement — and that now inherit the
|
|
34
|
+
replacement's access policy.
|
|
35
|
+
- **Ships as a Claude Code and Codex plugin**, with MCP resources and prompts,
|
|
36
|
+
a `planka-mcp init --client` installer, and a preflight hook.
|
|
37
|
+
- **Breaking changes**: `success` is no longer `true` on an unverified write;
|
|
38
|
+
`planka_list_cards` and `planka_list_lists` are deprecated (still callable);
|
|
39
|
+
`withDescription` and `labelFilter` are still accepted but no longer announced
|
|
40
|
+
in `inputSchema`; `planka_find_cards` digests now carry `labelIds`;
|
|
41
|
+
`planka_get_card` returns `listName: null` plus `listNameUnavailable` instead
|
|
42
|
+
of omitting the field.
|
|
43
|
+
|
|
44
|
+
### Fixed — adversarial review before release
|
|
45
|
+
|
|
46
|
+
- **`PLANKA_DEFAULT_BOARD_ID` no longer breaks the tool the instructions tell
|
|
47
|
+
you to call first.** The runtime presence check validated against the raw
|
|
48
|
+
registry schema while `tools/list` advertised the policy-applied one, so
|
|
49
|
+
`planka_board_summary {}` was refused for a missing `boardId` the listing said
|
|
50
|
+
was optional — on the five tools that declare it (`board_summary`,
|
|
51
|
+
`list_lists`, `get_board`, `create_list`, `create_label`). Both sides now go
|
|
52
|
+
through `effectiveInputSchema(tool, policy)`.
|
|
53
|
+
- **Deprecated aliases no longer bypass `PLANKA_DISABLED_TOOLS`.**
|
|
54
|
+
`planka_manage_lists {action:"delete"}` reached a disabled
|
|
55
|
+
`planka_delete_list`, and `planka_add_comment` a disabled
|
|
56
|
+
`planka_create_comment`. Both now consult the target's policy before
|
|
57
|
+
dispatching.
|
|
58
|
+
- **No more `{"success":true,"verified":false,"message":"… not confirmed"}`.**
|
|
59
|
+
That was the default branch of `update_task` and `delete_task` without
|
|
60
|
+
`cardId`, of `delete_attachment` when Planka did not echo the card, and of the
|
|
61
|
+
deletes whose response body does not parse. Unverified writes answer
|
|
62
|
+
`success: false` + `isError` and say what to pass; `update_task` without
|
|
63
|
+
`cardId` also labels its fields as `taskFromWriteEcho`.
|
|
64
|
+
- **The test guarding `WRITE_TOOL_NAMES` could not fail.** It compared a set
|
|
65
|
+
against its own complement. It now asserts, tool by tool, that membership
|
|
66
|
+
matches the `readOnlyHint` the tool advertises — the only net that stops the
|
|
67
|
+
next write tool from staying visible under `PLANKA_READ_ONLY=true`.
|
|
68
|
+
- **`PLANKA_PROTECTED_LIST_IDS` protects the cards, not just the column.**
|
|
69
|
+
`move_card`, `archive_card` and `delete_card` refused to write *into* a
|
|
70
|
+
protected column but happily emptied it one card at a time. The origin column
|
|
71
|
+
is now checked too — with no extra read on deployments that set no protected
|
|
72
|
+
list.
|
|
73
|
+
- **`planka_add_attachment` goes through `verifiedWrite`.** A failed re-read
|
|
74
|
+
used to return an error without the attachment id, and the natural retry
|
|
75
|
+
uploaded the file twice.
|
|
76
|
+
- **Four hand-written `verified: true` are gone.** `set_card_labels` and
|
|
77
|
+
`set_card_members` report through `writeResult` with a flag derived from their
|
|
78
|
+
own re-read; the two "nothing to do" states say which read backs the claim.
|
|
79
|
+
- **`planka_create_card` verifies what it wrote** (`name` and `listId`, not just
|
|
80
|
+
the id it re-read by), and a label that failed to attach comes back as
|
|
81
|
+
`{labelId, reason}` instead of a bare id — a 403 on the board and a wrong
|
|
82
|
+
labelId are otherwise indistinguishable.
|
|
83
|
+
- **`planka_find_cards` digests carry `labelIds`**, so
|
|
84
|
+
`find_cards → set_card_labels` no longer needs a `planka_board_summary` in
|
|
85
|
+
between, and multi-column results carry `listId` next to `listName`.
|
|
86
|
+
- **A 404 on the board keeps its diagnosis** when `/api/projects` is unreadable
|
|
87
|
+
too: the original error, with its hint, is re-thrown instead of replaced by a
|
|
88
|
+
bare "Board not found".
|
|
89
|
+
- **`planka_create_list` and `planka_create_label` check the board allowlist
|
|
90
|
+
explicitly**, instead of relying on a lower layer to do it.
|
|
91
|
+
- `planka_get_card` returns `listName: null` with `listNameUnavailable` when the
|
|
92
|
+
board could not be read; a 404 on `/api/users/me` no longer claims `me` should
|
|
93
|
+
be a `userId`; the HTML-instead-of-JSON error only blames an HTTP→HTTPS
|
|
94
|
+
redirect when the URL was `http://` or the request was actually redirected;
|
|
95
|
+
`planka_set_card_members` with names and no board says the problem was
|
|
96
|
+
resolving a **name**; the `planka://labels/colors` resource points at
|
|
97
|
+
`planka_create_label` / `planka_update_label` instead of the deprecated
|
|
98
|
+
`planka_manage_labels`.
|
|
99
|
+
|
|
100
|
+
### Changed — `tools/list` cost
|
|
101
|
+
|
|
102
|
+
- `planka_list_cards` and `planka_list_lists` joined `DEPRECATED_TOOL_NAMES`,
|
|
103
|
+
the 43-colour enum is announced once (on `planka_create_label`) instead of
|
|
104
|
+
three times, `withDescription` and `labelFilter` are no longer announced, and
|
|
105
|
+
the `boardId` description was cut. Measured over JSON-RPC against the built
|
|
106
|
+
server: `tools/list` **34 477 → 32 840** characters, and **29 821 → 26 034**
|
|
107
|
+
with `PLANKA_HIDE_DEPRECATED=true` (−22 % against the pre-release baseline).
|
|
108
|
+
Nothing lost capability: every trimmed parameter is still accepted.
|
|
109
|
+
|
|
110
|
+
### Documentation
|
|
111
|
+
|
|
112
|
+
- `server.json` declares all thirteen environment variables the code reads;
|
|
113
|
+
`PLANKA_MCP_PREFLIGHT` and `PLANKA_MCP_COMMAND` are documented in both READMEs;
|
|
114
|
+
`docs/tools.md` lists the eleven `requiresUserInteraction` tools, the complete
|
|
115
|
+
data-losing set, `position` on `planka_create_label`, and the shared digest
|
|
116
|
+
vocabulary; `scripts/sync-identity.mjs` now also stamps the two `SKILL.md`
|
|
117
|
+
versions, so `check:identity` catches them when they go stale.
|
|
118
|
+
|
|
119
|
+
### Added
|
|
120
|
+
|
|
121
|
+
- **The three `manage_*` tools are split, one tool per action: 32 → 41.**
|
|
122
|
+
`planka_create_label` / `planka_update_label` / `planka_delete_label`,
|
|
123
|
+
`planka_create_list` / `planka_update_list` / `planka_delete_list`, and
|
|
124
|
+
`planka_create_comment` / `planka_update_comment` / `planka_delete_comment`.
|
|
125
|
+
Each declares a real `required` in its JSON Schema, so the client refuses an
|
|
126
|
+
incomplete call before it leaves and the server's generic presence check does
|
|
127
|
+
the rest. The `action` enum could not express "boardId is required when
|
|
128
|
+
action=create", which is why every `manage_*` carried 20-40 lines of
|
|
129
|
+
hand-written guards — and a forgotten branch is exactly how an `update`
|
|
130
|
+
carrying only an id shipped an empty `PATCH` that Planka answered `200` to.
|
|
131
|
+
All nine reuse the existing operations and `verifiedWrite`/`verifiedDelete`,
|
|
132
|
+
and answer with the same digest (`{id, name, …, verified}`) as before.
|
|
133
|
+
`planka_delete_list` says in its own description that it deletes every card in
|
|
134
|
+
the column.
|
|
135
|
+
- `PLANKA_HIDE_DEPRECATED=true` drops the deprecated tools from `tools/list`:
|
|
136
|
+
`planka_manage_labels`, `planka_manage_lists`, `planka_manage_comment`,
|
|
137
|
+
`planka_add_comment`, `planka_get_board` and — added at the end of this
|
|
138
|
+
release — `planka_list_cards` and `planka_list_lists`. Measured on `tools/list`
|
|
139
|
+
at 0.3.0: **32 840 → 26 034 characters** (−6 806, −20.7 %). They remain
|
|
140
|
+
callable, so a model working from a cached list or from a skill that names one
|
|
141
|
+
still gets the real behaviour instead of "unknown tool". Default `false`.
|
|
142
|
+
- 35 unit tests for the split (`tests/unit/split-manage.test.ts`): the happy path
|
|
143
|
+
of each new tool against a mocked re-read, the `nothing to do` guard, a delete
|
|
144
|
+
that did not delete, `required` enforcement per tool, every alias dispatching,
|
|
145
|
+
and the `PLANKA_HIDE_DEPRECATED` filter. Suite: 189 → 224 tests.
|
|
146
|
+
- **The repository is now a plugin, for Claude Code and for Codex.**
|
|
147
|
+
`.claude-plugin/plugin.json` and `.codex-plugin/plugin.json` install the MCP
|
|
148
|
+
server plus the two `workflow/skills/` in one step, and the repo is its own
|
|
149
|
+
marketplace (`.claude-plugin/marketplace.json`,
|
|
150
|
+
`.agents/plugins/marketplace.json`):
|
|
151
|
+
`/plugin marketplace add omnicoreos/planka-mcp` → `/plugin install planka@planka-mcp`.
|
|
152
|
+
`userConfig` asks for the base URL and the API key when the plugin is enabled
|
|
153
|
+
and stores the key in the OS keychain — the manifest itself holds no secret,
|
|
154
|
+
only `${user_config.*}` substitutions. Claude Code reads `.mcp.json`
|
|
155
|
+
(`mcpServers`), Codex reads `.codex-plugin/mcp.json` (`mcp_servers`); the two
|
|
156
|
+
files exist because the wrapper key differs and one file cannot be both
|
|
157
|
+
without Codex reading a server literally named `mcpServers`. Validated with
|
|
158
|
+
`claude plugin validate --strict`.
|
|
159
|
+
- **A `SessionStart` preflight** (`hooks/preflight.mjs`): catches a Node older
|
|
160
|
+
than 18, a missing credential, an API key set together with email+password,
|
|
161
|
+
and an `http://` base URL that a redirect would turn into a dropped POST body.
|
|
162
|
+
Offline, silent when everything is fine, and it never exits non-zero — a
|
|
163
|
+
broken preflight must not block a session. `PLANKA_MCP_PREFLIGHT=full` also
|
|
164
|
+
spawns the server and waits for its banner.
|
|
165
|
+
- **MCP resources** (`src/resources.ts`): `planka://workflow/readme`,
|
|
166
|
+
`planka://workflow/board-template`, `planka://workflow/skills/orchestrator`,
|
|
167
|
+
`planka://workflow/skills/close-card`, `planka://gotchas/planka-2x` and
|
|
168
|
+
`planka://labels/colors`. The first five are read from the tarball at runtime
|
|
169
|
+
(resolved from `import.meta.url`, with a note pointing at the repository if an
|
|
170
|
+
install trimmed them); the last is generated from `LabelColorSchema`, so the
|
|
171
|
+
43 colors can never drift from what the tools validate. A resource costs
|
|
172
|
+
nothing until it is read, which is where the 25 000 chars of `workflow/` and
|
|
173
|
+
`docs/` belong — not in `instructions`, which every session pays for.
|
|
174
|
+
- **MCP prompts** (`src/prompts.ts`): `planka-open-session`,
|
|
175
|
+
`planka-close-card` and `planka-board-triage`, surfaced by Claude Code as
|
|
176
|
+
`/mcp__planka__…`. Each orders the calls and says what to look at in the
|
|
177
|
+
answers, without restating what tool descriptions already say. Arguments are
|
|
178
|
+
optional except `cardId`, and a missing required argument is refused by name.
|
|
179
|
+
- **`planka-mcp init --client claude|codex|cursor|vscode|print`**: one setup
|
|
180
|
+
command for every client. Emits `claude mcp add`, a `[mcp_servers.planka]`
|
|
181
|
+
TOML block, a merged `~/.cursor/mcp.json` plus an "Add to Cursor" deeplink,
|
|
182
|
+
and a `.vscode/mcp.json` whose key is a `promptString` input rather than a
|
|
183
|
+
literal. Every emitter merges non-destructively — an existing `planka` entry
|
|
184
|
+
is reported and left alone — files written into `$HOME` get mode `0600`, and
|
|
185
|
+
no printed snippet or deeplink ever carries a real credential. Before writing
|
|
186
|
+
it runs one authenticated `GET /api/users/me`, which is the check that catches
|
|
187
|
+
a base URL pointing at the SPA instead of the API. `--dry-run` writes nothing;
|
|
188
|
+
`--client print` needs no credentials at all.
|
|
189
|
+
- README and README.es: an **Install** table with a one-liner per client
|
|
190
|
+
(Claude Code plugin and server, Codex, Cursor badge, VS Code badge, `init`)
|
|
191
|
+
and a **Resources and prompts** section.
|
|
192
|
+
|
|
193
|
+
- **`planka_get_card` is a digest with detail on demand.** By default it returns
|
|
194
|
+
counts and names instead of the whole record: `listName` next to `listId`,
|
|
195
|
+
label **names** with their ids, `members`, tasks as `{done, total, lists:[…]}`
|
|
196
|
+
with one entry per checklist, `attachmentsCount`, Planka's own `commentsTotal`,
|
|
197
|
+
`dueDate`/`isDueCompleted`/`isClosed`/`updatedAt`, and a 200-char
|
|
198
|
+
`descriptionPreview` (with `descriptionChars`, `descriptionTruncated`).
|
|
199
|
+
New parameters: `detail: "digest" | "full"`, `withComments` (a number or
|
|
200
|
+
`"all"`), `descriptionChars`. Measured on a real card with 6 comments and 5
|
|
201
|
+
attachments: **1275 chars against 13 187** for the old always-everything
|
|
202
|
+
answer — 90 % less, with the column name and the label names it never had.
|
|
203
|
+
- `planka_get_comments` paginates honestly: `limit` (default 20, max 50),
|
|
204
|
+
`beforeId` as an in/out cursor, `hasMore`, `nextBeforeId`, `order`
|
|
205
|
+
(`"asc"` — oldest first — by default) and `all: true` with a 10-page ceiling
|
|
206
|
+
that reports `truncated`. `getComments()` / `getCommentsPage()` in
|
|
207
|
+
`src/operations/comments.ts` are the operations behind it.
|
|
208
|
+
- `getBoardNames()`: the one board read that resolves a card's `listId` and
|
|
209
|
+
`labelId`s to names, memoized for 60 s. Best effort by design — a board that
|
|
210
|
+
cannot be read costs the names, never the card.
|
|
211
|
+
- 23 unit tests: digest vs full, `withComments` 0/N/`"all"` against a mocked
|
|
212
|
+
two-page endpoint, checklists that stay apart, members, the description
|
|
213
|
+
preview, the comment cursor and its ceiling, and one per error branch.
|
|
214
|
+
Suite: 109 → 132 tests.
|
|
215
|
+
- **Eight new tools, 24 → 32.** All of them answer with a digest and every write
|
|
216
|
+
re-reads before reporting:
|
|
217
|
+
- `planka_card_history` and `planka_board_activity` read Planka's activity log
|
|
218
|
+
(`/api/cards/:id/actions`, `/api/boards/:id/actions`) and turn each event
|
|
219
|
+
into one human line — "moved from X to Y", "added member Z", "completed task
|
|
220
|
+
T". `board_activity` walks pages backwards to a `since` date (5-page cap,
|
|
221
|
+
reported as `truncated`) and groups the result by card. Both say, when they
|
|
222
|
+
return nothing, that Planka logs only creation, moves, member changes and
|
|
223
|
+
task completion — never comments, renames or description edits.
|
|
224
|
+
- `planka_set_card_members` assigns and unassigns people, accepting user IDs
|
|
225
|
+
or names, and reports `finalMemberIds` read back from the card. The mirror
|
|
226
|
+
of `planka_set_card_labels`, argument for argument.
|
|
227
|
+
- `planka_list_users` lists who can be assigned, falling back to the board's
|
|
228
|
+
own members when `GET /api/users` is refused (it is admin-only) and saying
|
|
229
|
+
so in `source`. Emails only with `detail: "full"`.
|
|
230
|
+
- `planka_whoami` names the account, its board role, the Planka version and
|
|
231
|
+
the local access policy — the first call to make when a `404` makes no
|
|
232
|
+
sense. Warns when the instance is not a Planka 2.x.
|
|
233
|
+
- `planka_duplicate_card` copies a card with its tasks, labels and members
|
|
234
|
+
(attachments are not copied) and reports the counts from re-reading the copy.
|
|
235
|
+
- `planka_archive_card` archives into the board's hidden `archive` list and
|
|
236
|
+
restores with `restoreToListId`. Verified by the card's column: `isClosed`
|
|
237
|
+
does NOT change on archive, so it proves nothing.
|
|
238
|
+
- `planka_move_list_cards` empties one column into another, counting both
|
|
239
|
+
columns before and after. It uses Planka's bulk endpoint only for the case
|
|
240
|
+
that endpoint accepts (a closed column into the archive) and moves card by
|
|
241
|
+
card otherwise, preserving each card's position; `strategy` reports which.
|
|
242
|
+
- `src/operations/card-brief.ts`: a permissive card read used for verification.
|
|
243
|
+
An archived card comes back with `position: null`, which the strict card
|
|
244
|
+
schema rejects — the archive tool could not have verified its own write
|
|
245
|
+
without it. It also exposes `cardMemberships`, which no other read did.
|
|
246
|
+
- `countListCards()` and `moveListCards()` in `src/operations/lists.ts`, plus
|
|
247
|
+
`src/operations/{actions,members,users,archive,duplicate}.ts`.
|
|
248
|
+
- The four new write tools are in `WRITE_TOOL_NAMES`, so `PLANKA_READ_ONLY`
|
|
249
|
+
hides them, and `planka_archive_card` and `planka_move_list_cards` carry
|
|
250
|
+
`anthropic/requiresUserInteraction`: both change what a whole column looks
|
|
251
|
+
like.
|
|
252
|
+
- 32 unit tests for the new surface (fetch mocked): one summary per event type,
|
|
253
|
+
the `since` sweep and its cursor, the 403 fallback of the user roster, the
|
|
254
|
+
archive-list resolution, and the two failures that must not read as success —
|
|
255
|
+
a membership that never landed and a bulk move whose counts do not add up.
|
|
256
|
+
Suite: 109 → 141 tests.
|
|
257
|
+
- Smoke: the new tools are exercised end to end and cross-checked against the
|
|
258
|
+
raw API — history after a real move and a real assignment, a duplicate, an
|
|
259
|
+
archive/restore round trip, and a bulk move between the smoke's temporary
|
|
260
|
+
column and its working column. Every card it creates is deleted in the
|
|
261
|
+
`finally`.
|
|
262
|
+
- Server instructions gained one line: `planka_board_activity` is the call to
|
|
263
|
+
make when resuming a board someone else left.
|
|
264
|
+
|
|
265
|
+
- `src/operations/verify.ts`: `verifiedWrite`, `verifiedDelete` and `goneOn404`,
|
|
266
|
+
the single implementation of "write, re-read, report what landed". Until now
|
|
267
|
+
the pattern lived hand-copied in two places and was missing from eleven writes.
|
|
268
|
+
- Every write reports `verified: true|false`, and `isError` when the re-read
|
|
269
|
+
contradicts it or could not be made. New response fields: `verificationError`,
|
|
270
|
+
`verificationUnavailable`, `partialFailure`, `notLanded`.
|
|
271
|
+
- `planka_move_card` accepts `boardId`, so a card can finally be moved between
|
|
272
|
+
boards (the operation supported it; the tool did not expose it).
|
|
273
|
+
- `planka_update_task`, `planka_delete_task` and `planka_delete_attachment`
|
|
274
|
+
accept an optional `cardId`, which is what makes their verification possible
|
|
275
|
+
(Planka 2.x has no GET for a single task).
|
|
276
|
+
- `getCardLight()`: a card read without its comments, used for verification and
|
|
277
|
+
by `planka_set_card_labels`, which was pulling every comment of a card twice
|
|
278
|
+
only to compare label ids.
|
|
279
|
+
- 19 unit tests for the false greens: comments endpoint failing, partial task
|
|
280
|
+
creation, deletes that do not delete, no-op PATCHes, a move that did not move,
|
|
281
|
+
and labels that never attached. Suite: 45 → 64 tests.
|
|
282
|
+
- Smoke: a REAL move between two columns (temporary column created, card moved
|
|
283
|
+
there and back, both directions cross-checked against the raw API) and
|
|
284
|
+
`verified: true` assertions on every write.
|
|
285
|
+
|
|
286
|
+
- **Board reads redesigned around one principle: a small digest by default, the
|
|
287
|
+
detail through parameters.** `planka_find_cards` is now the single read over
|
|
288
|
+
cards and takes `listId`, `listName`, `query`, `labelName` (alias
|
|
289
|
+
`labelFilter`), `labelIds`, `userIds`, `withLabels`, `detail`
|
|
290
|
+
(`"digest" | "full"`), `withDescription`, `descriptionChars`, `limit` and
|
|
291
|
+
`offset`.
|
|
292
|
+
- `planka_find_cards` with a `listId` reads the column through
|
|
293
|
+
`GET /api/lists/:id`: **one request, no cursor**, so `total` is the real size
|
|
294
|
+
of the column and the answer carries `truncated: false`. A 159-card column
|
|
295
|
+
went from five requests to two (one with `withLabels: false`).
|
|
296
|
+
- The card digest now carries `members`, `commentsTotal` and `updatedAt`, and
|
|
297
|
+
`detail: "full"` returns every field Planka gave, description included.
|
|
298
|
+
- `planka_board_summary` absorbs `planka_list_lists`: `cardsFrom: []` answers
|
|
299
|
+
columns and labels with no cards. It also reports `totalCards`.
|
|
300
|
+
- `warnings` on `planka_board_summary` and `planka_find_cards`: a `cardsFrom`
|
|
301
|
+
entry, a `listName` or a `highlightLabel` that matches nothing comes back
|
|
302
|
+
naming what does exist, instead of an empty result that reads like "there is
|
|
303
|
+
nothing there".
|
|
304
|
+
- `PLANKA_SUMMARY_DECISION_LISTS` and `PLANKA_SUMMARY_HIGHLIGHT_LABEL`: the
|
|
305
|
+
board vocabulary that used to be hardcoded in Spanish inside the server now
|
|
306
|
+
comes from the environment, and defaults to nothing.
|
|
307
|
+
- `planka_get_structure` takes `withLists` (default `true`). With `false` the
|
|
308
|
+
whole structure is ONE request instead of one board read per board — 203
|
|
309
|
+
characters against 696 on the reference instance.
|
|
310
|
+
- `planka_get_board` takes `limit` (default 50) and reports `totalCards`,
|
|
311
|
+
`returnedCards` and `truncated`.
|
|
312
|
+
- `excludesArchived: true` on every read derived from the board show, and a new
|
|
313
|
+
gotcha documenting why: `GET /api/boards/:id` carries no archive and no trash
|
|
314
|
+
cards, and there is no way to ask for them.
|
|
315
|
+
- `query` semantics are now Planka's own: several words are ANDed, and a leading
|
|
316
|
+
`/` makes the rest a case-insensitive regular expression. A broken regex is
|
|
317
|
+
reported instead of matching nothing.
|
|
318
|
+
- 18 unit tests for the redesigned reads (list path, digest vs full, aliases,
|
|
319
|
+
warnings, derived page size, `excludesArchived`, `withLists`). Suite: 116 →
|
|
320
|
+
134 tests.
|
|
321
|
+
|
|
322
|
+
### Deprecated
|
|
323
|
+
|
|
324
|
+
- **`planka_get_board`.** Its description now starts with
|
|
325
|
+
`DEPRECATED: use planka_board_summary + planka_find_cards`. It stays
|
|
326
|
+
functional, and it is capped: 56,112 → 17,215 characters on the reference
|
|
327
|
+
board.
|
|
328
|
+
- `planka_list_lists` and `planka_list_cards` are kept as **aliases** —
|
|
329
|
+
respectively `planka_board_summary` with `cardsFrom: []` and
|
|
330
|
+
`planka_find_cards` with `listId`. Same implementation, same response shape;
|
|
331
|
+
the tool count and every tool name are unchanged.
|
|
332
|
+
|
|
333
|
+
### Changed
|
|
334
|
+
|
|
335
|
+
- **The old names survive as deprecated aliases.** `planka_manage_labels`,
|
|
336
|
+
`planka_manage_lists`, `planka_manage_comment` and `planka_add_comment` keep
|
|
337
|
+
working unchanged; each is a ten-line dispatcher that routes `action` to the
|
|
338
|
+
tool that replaced it and borrows that tool's `required` (so
|
|
339
|
+
`manage_labels action:update` with no `labelId` is refused with a message
|
|
340
|
+
naming `planka_update_label`). Their descriptions open with `DEPRECATED:`.
|
|
341
|
+
- The nine new tools are in `WRITE_TOOL_NAMES` (18 → 27), so `PLANKA_READ_ONLY`
|
|
342
|
+
hides them, and in `src/tools/annotations.ts`; the three `delete_*` carry
|
|
343
|
+
`anthropic/requiresUserInteraction`.
|
|
344
|
+
- The smoke suite drives labels, lists and comments through the new tools and
|
|
345
|
+
keeps one check per alias, plus one that the alias borrows its target's
|
|
346
|
+
`required`.
|
|
347
|
+
- `scripts/setup.sh` is now an alias for `init --client claude`. The guided
|
|
348
|
+
installer that also picks or creates a board and runs the full write smoke
|
|
349
|
+
stays available as `node scripts/setup.mjs`.
|
|
350
|
+
- `workflow/skills/*/SKILL.md`: frontmatter trimmed to the portable
|
|
351
|
+
agentskills.io subset (`version:` moved under `metadata:`), and the bodies
|
|
352
|
+
updated to the current read surface — `board_summary` as the only opening
|
|
353
|
+
read, `find_cards` with a `listId` for a whole column, `get_card` as a digest
|
|
354
|
+
with `withComments`, `board_activity` when resuming someone else's work,
|
|
355
|
+
`verified` on every write, and `get_board`/`list_lists`/`list_cards` named as
|
|
356
|
+
deprecated aliases.
|
|
357
|
+
- `scripts/sync-identity.mjs` also generates the version pin in `.mcp.json`,
|
|
358
|
+
`.codex-plugin/mcp.json` and both plugin manifests, and exports
|
|
359
|
+
`PACKAGE_NAME`. A pin that drifts would install the old server from a new
|
|
360
|
+
plugin, silently; `npm run check:identity` now fails the build instead.
|
|
361
|
+
- `package.json` ships `.claude-plugin`, `.codex-plugin`, `.agents`,
|
|
362
|
+
`.mcp.json` and `hooks` in the tarball.
|
|
363
|
+
- 36 unit tests for the new surface (resources list/read and mime types,
|
|
364
|
+
prompts, the `init` emitters and merges, and the plugin manifests, which
|
|
365
|
+
nothing else type-checks).
|
|
366
|
+
|
|
367
|
+
- **`planka_get_card` no longer returns comments by default.** They were the
|
|
368
|
+
biggest and least predictable part of the answer and most reads do not need
|
|
369
|
+
them; `withComments` brings them back, and the digest always says how many
|
|
370
|
+
exist (`commentsTotal`). Comments come back **oldest first**, the order the
|
|
371
|
+
thread was written.
|
|
372
|
+
- `commentCount` in `planka_get_comments` is documented and reported as what
|
|
373
|
+
*that response* carries, never as the card's total — Planka serves comments 50
|
|
374
|
+
at a time and ignores `?limit`/`?page`, so the old field was a page size
|
|
375
|
+
wearing a total's name.
|
|
376
|
+
- Errors say what to do next: a **403** names the likely cause (the agent user is
|
|
377
|
+
not a board member, or is a viewer where an editor is needed) and warns that
|
|
378
|
+
Planka answers 404 for it too; a **404** keeps Planka's own message and names
|
|
379
|
+
the kind of ID the path expected (`labelId`, `listId`…); a **401** that
|
|
380
|
+
survives the automatic retry points at `PLANKA_API_KEY` /
|
|
381
|
+
`PLANKA_AGENT_PASSWORD` instead of at the session; **HTML instead of JSON**
|
|
382
|
+
names the SPA catch-all and, on a write, warns that it MAY have landed;
|
|
383
|
+
**network failures** carry the base URL.
|
|
384
|
+
- `planka_view_attachment` on a card with no attachments says there is nothing
|
|
385
|
+
to view, instead of asking for an `attachmentId` and listing "none".
|
|
386
|
+
- `CardIncludedSchema` covers what Planka 2.2 really sends (`cardMemberships`,
|
|
387
|
+
`users`, `customFields`, `customFieldValues`) and the card schema tolerates
|
|
388
|
+
`commentsTotal`, `isDueCompleted`, `isClosed` and `stopwatch`. Enums stay open
|
|
389
|
+
strings on read paths.
|
|
390
|
+
- **Read response shapes changed** (the tool names did not): `planka_list_cards`
|
|
391
|
+
answers `matches` where it used to answer `cards`, `warning` (a string) became
|
|
392
|
+
`warnings` (an array) on `planka_find_cards`, the per-card `list` is now
|
|
393
|
+
`listName` and appears only when the result spans several columns, and
|
|
394
|
+
`planka_board_summary` no longer defaults to Spanish column and label names.
|
|
395
|
+
- **A `200` is no longer a success.** `planka_create_card`, `planka_update_card`,
|
|
396
|
+
`planka_move_card`, `planka_delete_card`, `planka_create_tasks`,
|
|
397
|
+
`planka_update_task`, `planka_delete_task`, `planka_manage_labels`,
|
|
398
|
+
`planka_add_comment`, `planka_manage_comment`, `planka_manage_lists` and
|
|
399
|
+
`planka_delete_attachment` now re-read before reporting.
|
|
400
|
+
- `planka_create_card` counts the tasks and labels **that are on the card**, not
|
|
401
|
+
the ones requested, always returns `card.id` (even when a follow-up step
|
|
402
|
+
fails, so a retry does not duplicate the card), and errors when every
|
|
403
|
+
requested label failed to attach.
|
|
404
|
+
- `planka_update_card`, `planka_update_task`, `planka_manage_labels update` and
|
|
405
|
+
`planka_manage_lists update` reject a request carrying only an id
|
|
406
|
+
(`nothing to do`) instead of sending an empty PATCH and reporting success.
|
|
407
|
+
- `planka_manage_lists` says in its description and in the docs that deleting a
|
|
408
|
+
list deletes every card in it.
|
|
409
|
+
- `PlankaClient.delete()` returns the response body instead of `void`: Planka
|
|
410
|
+
2.x echoes the deleted item, and its parent id is what makes the confirmation
|
|
411
|
+
read possible.
|
|
412
|
+
- Label `color` is a real JSON Schema `enum` of the 43 colors instead of a
|
|
413
|
+
sentence in the description, so the client rejects a bad color before the
|
|
414
|
+
call. (Measured: 599 chars against 580 — this is a correctness win, not a
|
|
415
|
+
token saving.)
|
|
416
|
+
- Tool payloads are serialized without indentation (`JSON.stringify(x)`),
|
|
417
|
+
27–46 % fewer characters on the read-heavy tools at zero information cost.
|
|
418
|
+
- Tool descriptions are shorter. The prose that was repeated across many of them
|
|
419
|
+
— that IDs are strings, that `planka_get_board` is the expensive call, the
|
|
420
|
+
meaning of `total`/`returned`/`hasMore` — now lives in the server
|
|
421
|
+
instructions, as the spec asks. The sum of all descriptions went from 3658 to
|
|
422
|
+
2093 characters; no `inputSchema` changed.
|
|
423
|
+
- `getToolDefinitions()` now propagates `title`, `annotations` and `_meta`. It
|
|
424
|
+
previously mapped only name, description and schema, which would have dropped
|
|
425
|
+
them from `tools/list` without any error.
|
|
426
|
+
|
|
427
|
+
### Fixed
|
|
428
|
+
|
|
429
|
+
- `planka_move_list_cards` answered 403 "Not enough rights" on any ordinary
|
|
430
|
+
column-to-column move. `POST /api/lists/:id/move-cards` is not the general
|
|
431
|
+
bulk move its name suggests: Planka 2.2.1 refuses anything but a `closed`
|
|
432
|
+
source into an `archive` destination, and the refusal reads like a permission
|
|
433
|
+
problem. The tool now falls back to one PATCH per card, keeps each card's
|
|
434
|
+
position, and reports which path it took plus any card that refused to move.
|
|
435
|
+
- Restoring a card out of the archive sent only `listId`, and Planka answered
|
|
436
|
+
*"Position must be present"* — an archived card has `position: null`. The
|
|
437
|
+
restore path now always sends a position. Caught against a real Planka 2.2.1,
|
|
438
|
+
not in a mock.
|
|
439
|
+
- `getCardsForList` derives Planka's page size from the first response instead
|
|
440
|
+
of assuming 50. An instance answering 20 per page used to stop on page one and
|
|
441
|
+
report a third of a column as the whole of it, with `truncated: false`.
|
|
442
|
+
- The card schema accepts `commentsTotal`, `isClosed` and `isDueCompleted`,
|
|
443
|
+
including the `null` Planka actually sends — reading them as booleans would
|
|
444
|
+
have made every board read fail.
|
|
445
|
+
- `planka_find_cards` no longer returns `listId` **and** `list` on every match:
|
|
446
|
+
a search scoped to one column names it once, at the root.
|
|
447
|
+
- `planka_get_card` no longer swallows a failing comments endpoint into
|
|
448
|
+
`comments: []`. A 403, a timeout or an HTML answer now surfaces as an error:
|
|
449
|
+
"this card has no comments" and "I could not read them" are different facts,
|
|
450
|
+
and on a board whose memory lives in the comments the difference is expensive.
|
|
451
|
+
- Server instructions in the `initialize` handshake
|
|
452
|
+
(`InitializeResult.instructions`, `src/instructions.ts`). Claude Code injects
|
|
453
|
+
them into the session system prompt and Codex CLI reads them alongside the
|
|
454
|
+
tool list, so the guidance shared by many tools is stated once: open with
|
|
455
|
+
`planka_board_summary` and never `planka_get_board`, IDs are strings of digits
|
|
456
|
+
that come from `planka_get_structure` or `planka_board_summary`, read every
|
|
457
|
+
comment on a card before acting, reads return digests with
|
|
458
|
+
`total`/`returned`/`hasMore`, writes report `verified` and `finalLabelIds`,
|
|
459
|
+
and Planka answers `404` where it means `403`. Budgeted at 1500-2500
|
|
460
|
+
characters, with the first 512 self-contained for clients that truncate.
|
|
461
|
+
- Tool annotations on all twenty-four tools: a display `title` plus
|
|
462
|
+
`readOnlyHint`, `destructiveHint`, `idempotentHint` and `openWorldHint`, every
|
|
463
|
+
field written out rather than inherited. The spec defaults are pessimistic, so
|
|
464
|
+
without them a conformant client had to treat `planka_get_comments` as being
|
|
465
|
+
as destructive as `planka_delete_card`.
|
|
466
|
+
- The two Anthropic `_meta` keys Claude Code actually acts on:
|
|
467
|
+
`anthropic/requiresUserInteraction` on `planka_delete_card`,
|
|
468
|
+
`planka_delete_task`, `planka_delete_attachment`, `planka_manage_comment`,
|
|
469
|
+
`planka_manage_lists` and `planka_manage_labels`, which forces the permission
|
|
470
|
+
prompt even under an `allow` rule; and `anthropic/maxResultSizeChars` on
|
|
471
|
+
`planka_view_attachment`, so a 3 MB base64 image is not clipped.
|
|
472
|
+
- Tests that keep all of the above honest: every registered tool must have an
|
|
473
|
+
annotations entry (a new tool without one fails the suite), the tools marked
|
|
474
|
+
as needing confirmation must carry the `_meta` flag, the instructions must fit
|
|
475
|
+
their budget, and `tools/list` must return a fixed, snapshotted tool order.
|
|
476
|
+
- **API key authentication.** `PLANKA_API_KEY` sends `X-Api-Key` on every
|
|
477
|
+
request — JSON calls, multipart uploads and binary attachment downloads alike
|
|
478
|
+
— with no login round-trip and no token refresh. It also sidesteps Planka's
|
|
479
|
+
sign-in rate limit of ten logins per identity per minute, which several agents
|
|
480
|
+
starting at once will otherwise hit. Configuration is now API key XOR
|
|
481
|
+
email/password: setting both is refused rather than guessed, because Planka
|
|
482
|
+
reads `Authorization` first and silently ignores `x-api-key` when both arrive.
|
|
483
|
+
- **Access policy driven by environment variables**, in `src/config/policy.ts`:
|
|
484
|
+
`PLANKA_DEFAULT_BOARD_ID`, `PLANKA_ALLOWED_BOARD_IDS`,
|
|
485
|
+
`PLANKA_ALLOWED_PROJECT_IDS`, `PLANKA_READ_ONLY`, `PLANKA_DISABLED_TOOLS` and
|
|
486
|
+
`PLANKA_PROTECTED_LIST_IDS`. Allowlists are by id, never by name: a name is one
|
|
487
|
+
edit away from being someone else's.
|
|
488
|
+
- **`boardId` is now optional** on every tool that takes one, whenever
|
|
489
|
+
`PLANKA_DEFAULT_BOARD_ID` is configured. It is removed from the tool's
|
|
490
|
+
`required` list and the description says where the default comes from, so an
|
|
491
|
+
agent stops calling `planka_get_structure` just to recover an id that never
|
|
492
|
+
changes.
|
|
493
|
+
- **`PLANKA_READ_ONLY` and `PLANKA_DISABLED_TOOLS` hide tools and refuse them.**
|
|
494
|
+
Hidden tools disappear from `tools/list`, and calling one anyway returns an
|
|
495
|
+
error that names the variable responsible and says not to retry — hiding alone
|
|
496
|
+
is not enough for a client working from a cached list.
|
|
497
|
+
- **Allowlist filtering in `planka_get_structure`**, so an agent never sees the
|
|
498
|
+
boards and projects it may not touch, and a `projectId` that does not exist now
|
|
499
|
+
answers with the visible projects instead of an empty array.
|
|
500
|
+
- `server.json`: the new environment variables, plus `title`, `websiteUrl` and
|
|
501
|
+
`icons` for the MCP Registry listing. `PLANKA_AGENT_EMAIL` and
|
|
502
|
+
`PLANKA_AGENT_PASSWORD` are no longer marked required.
|
|
503
|
+
- README (English and Spanish): *Authentication* — including the four-step recipe
|
|
504
|
+
for a scoped `boardUser` with an API key — and *Scoping the server*, with the
|
|
505
|
+
three-layer model and the variable matrix. New entries in
|
|
506
|
+
`docs/troubleshooting.md` for every new error message.
|
|
507
|
+
- Binary attachment downloads now retry once on a 401 with a fresh token, the way
|
|
508
|
+
every other request already did. Before, an expired token during a download
|
|
509
|
+
surfaced as a bare authentication failure.
|
|
510
|
+
- A `200` carrying the SPA's HTML instead of JSON — what Planka answers for any
|
|
511
|
+
path it does not route, and what an `http://` base URL redirected to `https://`
|
|
512
|
+
ends up producing — is now reported as such on every request, not only during
|
|
513
|
+
login.
|
|
514
|
+
|
|
5
515
|
## [0.2.0] - 2026-08-24
|
|
6
516
|
|
|
7
517
|
### Added
|