mcp-planka
# mcp-planka
MCP-сервер для [PLANKA](https://planka.app/) — канбан-менеджера задач. Позволяет
читать и управлять проектами, досками, списками, карточками (задачами), метками и
вложениями через протокол Model Context Protocol.
Сервер общается только с JSON-API PLANKA и не хранит состояние: каждый вызов —
это один HTTP-запрос с таймаутом, который никогда не бросает исключение и всегда
возвращает корректный ответ.
## Возможности
- Чтение проектов (`planka_read_projects`).
- Чтение досок рабочего пространства (`planka_list_boards`).
- Чтение карточек рабочего пространства (`planka_list_cards` / `planka_read_cards`).
- Чтение по идентификатору: проект, доска, карточка, задача, список, список задач.
- Коллекции (проекты/доски/карточки) собираются из `included` ответов PLANKA,
поскольку прямых GET-листингов для `/api/boards` и `/api/cards` API не предоставляет.
- Грациозная деградация: при отсутствии/просроченном ключе (HTTP 401) возвращается
`{ ok: false, error }`, вызов никогда не падает с исключением.
## Требования
- Node.js >= 20 (используются только встроенные модули: `fetch`, `AbortSignal`).
- Доступ к экземпляру PLANKA с API-ключом (`X-Api-Key`).
## Установка
```bash
git clone <repo-url>
cd mcp-planka
# внешних зависимостей нет, npm install не требуется
```
## Конфигурация
Сервер настраивается через переменные окружения:
| Переменная | Назначение | По умолчанию |
| ------------------------- | -------------------------------------------- | ------------------------- |
| `PLANKA_BASE_URL` | Базовый URL экземпляра PLANKA | `http://localhost:1337` |
| `PLANKA_AGENT_API_KEY` | API-ключ; передаётся заголовком `X-Api-Key` | _(обязателен)_ |
| `PLANKA_REQUEST_TIMEOUT_MS` | Таймаут запроса в мс | `8000` |
Пример запуска диспетчера (smoke-проверка без MCP-клиента):
```bash
export PLANKA_BASE_URL="http://192.168.100.100:1337"
export PLANKA_AGENT_API_KEY="<your-api-key>"
node src/index.js
```
## Запуск как MCP-сервера (stdio)
`src/server.mjs` — это готовый **stdio MCP-сервер** (JSON-RPC 2.0 поверх
newline-delimited JSON). Он реализует `initialize`, `notifications/initialized`,
`ping`, `tools/list` (с JSON-схемами всех инструментов) и `tools/call` (делегирует
в `calls()`). Внешних зависимостей нет — только Node builtins.
```bash
npm start # node src/server.mjs — точка входа MCP-сервера
npm run mcp # то же самое
```
Сервер читает переменные окружения из раздела «Конфигурация» и пишет в stdout
**только** JSON-RPC (весь лог — в stderr; при `PLANKA_MCP_DEBUG=1` подробно).
### Подключение в MCP-клиенте (Hermes agent, pi.dev и др.)
Зарегистрируйте сервер в блоке `mcpServers` вашего клиента:
```json
{
"mcpServers": {
"planka": {
"command": "node",
"args": ["/home/agent/workdir/mcp_planka/src/server.mjs"],
"env": {
"PLANKA_BASE_URL": "http://192.168.100.100:1337",
"PLANKA_AGENT_API_KEY": "<your-api-key>"
}
}
}
}
```
> **Примечание про pi.dev.** Кодинг-агент pi **не имеет нативного MCP-клиента**
> (см. `usage.md` в документации pi), поэтому MCP-сервер подключается к pi.dev
> только косвенно — через внешний MCP-клиент/мост либо pi-расширение, которое
> spawn-ит `src/server.mjs` и проксирует вызовы. Сам сервер написан по спецификации
> stdio-MCP и совместим с любым стандартным MCP-клиентом (Hermes, Claude Desktop,
> и т.п.).
## Запуск и тестирование
```bash
npm start # node src/server.mjs — точка входа MCP-сервера
npm test # node test/run.mjs — смоук-тест слоёв и диспетчера
```
Смоук-тест не падает при отсутствии ключа (проверяет грациозную деградацию) и
показывает реальные данные, когда ключ задан.
## Архитектура
Код разделён на три слоя, каждый со своей зоной ответственности:
- `src/planka-api.js` — **единственное место, работающее с сетью**. Формирует URL
(всегда под `/api`, добавляет канонический query `fields[]=resources` для
коллекций, чтобы PLANKA отдавал JSON, а не HTML-оболочку SPA), ставит заголовок
`X-Api-Key`, задаёт единый таймаут `AbortSignal.timeout`, детектит HTML-оболочку
и предоставляет один примитив `request()`.
- `src/planka-client.js` — нормализует два вида ответа PLANKA
(`{ items, included }` и `{ item, included }`) в единый контракт
`{ ok, status, data, error }`. Никогда не бросает исключений.
- `src/planka-ops.js` — операции чтения/записи поверх клиента. Гарантируют, что
результат — либо `{ ok, items, count, error }`, либо `{ ok, node, error }`.
- `src/index.js` — MCP-диспетчер `calls(params)`: по `params.name` выбирает
инструмент и всегда возвращает `{ items, count, ok }` (или `ok:false` с `error`
для неизвестного инструмента).
## Инструменты MCP
Чтение:
| Имя инструмента | Описание |
| ---------------------- | ----------------------------------------- |
| `planka_read_projects` | Список проектов |
| `planka_list_boards` | Список досок (через `included` проектов) |
| `planka_list_cards` | Список карточек (через `included` досок) |
| `planka_read_cards` | Псевдоним `planka_list_cards` |
| `planka_read_project` | Проект по id |
| `planka_read_board` | Доска по id |
| `planka_read_card` | Карточка по id |
| `planka_read_task` | Задача по id (см. ограничение ниже) |
Запись (требует прав на запись у пользователя Planka, см. ниже):
| Имя инструмента | Действие |
| -------------------------- | --------------------------------------------- |
| `planka_create_project` | Создать проект (`type`,`name`) |
| `planka_update_project` | Обновить проект |
| `planka_delete_project` | Удалить проект |
| `planka_create_board` | Создать доску в проекте |
| `planka_update_board` | Обновить доску |
| `planka_delete_board` | Удалить доску |
| `planka_create_card` | Создать карту в списке (`type`,`name`,`position`) |
| `planka_update_card` | Обновить карту |
| `planka_delete_card` | Удалить карту |
| `planka_add_card_labels` | Добавить метки карте |
| `planka_remove_card_labels`| Убрать метки у карты |
> **Ограничение `planka_read_task`.** В API PLANKA у `/api/tasks/{id}` нет
> метода GET (только `PATCH` и `DELETE`, подтверждено в `swagger.json`). Поэтому
> `planka_read_task` не может прочитать задачу по id и возвращает корректный
> `{ ok:false, error }` вместо вызова несуществующего эндпоинта. Чтение карточек
> (задач-карточек) выполняется через `planka_read_card` (`GET /api/cards/{id}` —
> есть), а подзадачи (`subtasks`) API не отдаёт (нет GET на `/tasks/{id}` и нет
> `/tasks/{id}/subtasks`), поэтому `listSubtasks`/`updateSubtask`/`deleteSubtask`
> возвращают `{ ok:false, error }`.
> Ввод инструмента передаётся в `calls({ name, arguments })`, где `arguments` —
> объект с полями `id` / `projectId` / `listId` / `cardId` / `fields` / `labelIds`.
## Требования к правам пользователя Planka
API-ключ принадлежит пользователю Planka. Чтение работает при любой роли с
доступом к ресурсу; запись требует соответствующих прав: создание проектов
нужна минимум роль «Владелец проекта» (иначе `POST /api/projects` → HTTP 404,
хотя тело валидно). Роли Planka (по возрастанию прав): «Пользователь доски»,
«Владелец проекта», «Админ». Подробнее — в `docs/api-notes.md`
(раздел «Права пользователя Planka»).
## Особенности работы с API PLANKA
- **Аутентификация** — только заголовок `X-Api-Key: <PLANKA_AGENT_API_KEY>`.
При 401 сервер возвращает `{ ok: false, error }`; повторных попыток и логирования
ключа нет.
- **Канонический query** — коллекционные эндпоинты отдают HTML-оболочку SPA, если
не передан `fields[]=resources`. Сервер добавляет его автоматически для списков.
- **Вложенные данные** — доски и карточки извлекаются из `included` ответов
`GET /api/projects/{id}` и `GET /api/boards/{id}`, так как прямых листингов
`/api/boards` и `/api/cards` API не имеет.
## Лицензия
MIT.
TDQS
Scored across 19 tools
Most tools target distinct resources (projects, boards, cards, labels), but there is an exact alias (planka_list_cards and planka_read_cards) and a non-functional planka_read_task that returns an error, creating potential for misselection. The remaining tools have clear boundaries.
All tools share the planka_ prefix and use verb-like actions (read, list, create, update, delete, add, remove). However, the pattern mixes 'read' and 'list' for similar operations, and the alias planka_read_cards deviates from the standard. Overall consistent but not perfect.
With 19 tools, the server provides comprehensive coverage of projects, boards, cards, and labels. The count is slightly on the higher side but justified by the domain. Redundancies like the alias and the dummy task tool slightly reduce efficiency, yet the scope is reasonable.
The server covers CRUD for projects, boards, and cards, but lacks essential list management (cards are created in lists, yet no list tools exist). Additionally, there is no label CRUD (only add/remove), and planka_read_task is a stub that always fails. These gaps hinder full workflow coverage.