@vitalyostanin/youtrack-mcp 0.10.2 → 0.11.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/README-ru.md +28 -13
- package/README.md +33 -16
- package/dist/package.json +8 -1
- package/dist/src/__tests__/config.test.d.ts +2 -0
- package/dist/src/__tests__/config.test.js +47 -0
- package/dist/src/__tests__/smoke.test.d.ts +2 -0
- package/dist/src/__tests__/smoke.test.js +7 -0
- package/dist/src/__tests__/youtrack-client.delete-link.test.d.ts +2 -0
- package/dist/src/__tests__/youtrack-client.delete-link.test.js +93 -0
- package/dist/src/__tests__/youtrack-client.normalize-error.test.d.ts +2 -0
- package/dist/src/__tests__/youtrack-client.normalize-error.test.js +71 -0
- package/dist/src/__tests__/youtrack-client.process-batch.test.d.ts +2 -0
- package/dist/src/__tests__/youtrack-client.process-batch.test.js +63 -0
- package/dist/src/__tests__/youtrack-client.read-pagination.test.d.ts +2 -0
- package/dist/src/__tests__/youtrack-client.read-pagination.test.js +75 -0
- package/dist/src/__tests__/youtrack-client.resolve-id.test.d.ts +2 -0
- package/dist/src/__tests__/youtrack-client.resolve-id.test.js +77 -0
- package/dist/src/__tests__/youtrack-client.single-flight.test.d.ts +2 -0
- package/dist/src/__tests__/youtrack-client.single-flight.test.js +82 -0
- package/dist/src/__tests__/youtrack-client.timeouts.test.d.ts +2 -0
- package/dist/src/__tests__/youtrack-client.timeouts.test.js +27 -0
- package/dist/src/__tests__/youtrack-client.url-encoding.test.d.ts +2 -0
- package/dist/src/__tests__/youtrack-client.url-encoding.test.js +60 -0
- package/dist/src/config.d.ts +2 -8
- package/dist/src/config.js +8 -0
- package/dist/src/server.js +28 -3
- package/dist/src/tools/__tests__/article-search-tools.test.d.ts +2 -0
- package/dist/src/tools/__tests__/article-search-tools.test.js +31 -0
- package/dist/src/tools/__tests__/attachment-tools.test.d.ts +2 -0
- package/dist/src/tools/__tests__/attachment-tools.test.js +93 -0
- package/dist/src/tools/__tests__/destructive-confirmation.test.d.ts +2 -0
- package/dist/src/tools/__tests__/destructive-confirmation.test.js +38 -0
- package/dist/src/tools/__tests__/issue-activity-tools.test.d.ts +2 -0
- package/dist/src/tools/__tests__/issue-activity-tools.test.js +69 -0
- package/dist/src/tools/__tests__/issue-search-tools.test.d.ts +2 -0
- package/dist/src/tools/__tests__/issue-search-tools.test.js +25 -0
- package/dist/src/tools/__tests__/issue-status-tools.test.d.ts +2 -0
- package/dist/src/tools/__tests__/issue-status-tools.test.js +98 -0
- package/dist/src/tools/__tests__/workitem-tools.test.d.ts +2 -0
- package/dist/src/tools/__tests__/workitem-tools.test.js +72 -0
- package/dist/src/tools/article-search-tools.js +30 -22
- package/dist/src/tools/article-tools.js +58 -12
- package/dist/src/tools/attachment-tools.d.ts +109 -0
- package/dist/src/tools/attachment-tools.js +119 -61
- package/dist/src/tools/issue-activity-tools.d.ts +126 -0
- package/dist/src/tools/issue-activity-tools.js +83 -60
- package/dist/src/tools/issue-link-tools.js +67 -14
- package/dist/src/tools/issue-list-tools.js +17 -3
- package/dist/src/tools/issue-search-tools.d.ts +3 -2
- package/dist/src/tools/issue-search-tools.js +13 -46
- package/dist/src/tools/issue-star-tools.d.ts +11 -0
- package/dist/src/tools/issue-star-tools.js +72 -40
- package/dist/src/tools/issue-status-tools.d.ts +148 -0
- package/dist/src/tools/issue-status-tools.js +80 -62
- package/dist/src/tools/issue-tools.js +200 -44
- package/dist/src/tools/project-tools.js +40 -8
- package/dist/src/tools/service-info.js +9 -1
- package/dist/src/tools/user-tools.js +59 -15
- package/dist/src/tools/users-activity-tools.d.ts +1 -1
- package/dist/src/tools/users-activity-tools.js +9 -6
- package/dist/src/tools/workitem-report-tools.js +49 -10
- package/dist/src/tools/workitem-tools.d.ts +307 -0
- package/dist/src/tools/workitem-tools.js +205 -85
- package/dist/src/types.d.ts +13 -0
- package/dist/src/utils/__tests__/file-download.test.d.ts +2 -0
- package/dist/src/utils/__tests__/file-download.test.js +77 -0
- package/dist/src/utils/__tests__/file-storage.test.d.ts +2 -0
- package/dist/src/utils/__tests__/file-storage.test.js +61 -0
- package/dist/src/utils/__tests__/path-safety.test.d.ts +2 -0
- package/dist/src/utils/__tests__/path-safety.test.js +49 -0
- package/dist/src/utils/__tests__/streaming-client.test.d.ts +2 -0
- package/dist/src/utils/__tests__/streaming-client.test.js +90 -0
- package/dist/src/utils/__tests__/validators.test.d.ts +2 -0
- package/dist/src/utils/__tests__/validators.test.js +65 -0
- package/dist/src/utils/file-download.d.ts +12 -3
- package/dist/src/utils/file-download.js +76 -28
- package/dist/src/utils/file-storage.d.ts +27 -15
- package/dist/src/utils/file-storage.js +127 -121
- package/dist/src/utils/issue-query.js +17 -10
- package/dist/src/utils/mappers.js +7 -7
- package/dist/src/utils/path-safety.d.ts +18 -0
- package/dist/src/utils/path-safety.js +47 -0
- package/dist/src/utils/streaming-client.d.ts +13 -7
- package/dist/src/utils/streaming-client.js +69 -84
- package/dist/src/utils/validators.d.ts +17 -0
- package/dist/src/utils/validators.js +46 -0
- package/dist/src/youtrack-client.d.ts +83 -14
- package/dist/src/youtrack-client.js +354 -152
- package/package.json +8 -1
package/README-ru.md
CHANGED
|
@@ -40,6 +40,8 @@ MCP сервер для полноценной интеграции с YouTrack
|
|
|
40
40
|
- [Конфигурация для VS Code Cline](#конфигурация-для-vs-code-cline)
|
|
41
41
|
- [Инструменты MCP](#инструменты-mcp)
|
|
42
42
|
- [Параметры сохранения файлов](#параметры-сохранения-файлов)
|
|
43
|
+
- [Пагинация](#пагинация)
|
|
44
|
+
- [Подтверждение деструктивных операций](#подтверждение-деструктивных-операций)
|
|
43
45
|
- [Сервис](#сервис)
|
|
44
46
|
- [Задачи](#задачи)
|
|
45
47
|
- [Связи задач](#связи-задач)
|
|
@@ -62,6 +64,7 @@ MCP сервер для полноценной интеграции с YouTrack
|
|
|
62
64
|
- `YOUTRACK_PRE_HOLIDAYS` — опциональный список предпраздничных дат через запятую, для которых норма времени уменьшается автоматически
|
|
63
65
|
- `YOUTRACK_USER_ALIASES` — опциональный список соответствий `alias:login` через запятую (например, `me:vyt,petya:p.petrov`), используется при автоматическом выборе исполнителей
|
|
64
66
|
- `YOUTRACK_DEFAULT_PROJECT` — опциональный код проекта (shortName или ID), используемый по умолчанию при создании задач/статей и для преобразования коротких идентификаторов (например, `1234` → `PROJ-1234`)
|
|
67
|
+
- `YOUTRACK_OUTPUT_DIR` — опциональный корневой каталог для файлов, создаваемых через `saveToFile` и `downloadToFile`. По умолчанию используется текущий рабочий каталог. Абсолютные пути и сегменты с `..` (выход за пределы корня) отвергаются ради безопасности
|
|
65
68
|
|
|
66
69
|
|
|
67
70
|
## Установка
|
|
@@ -278,11 +281,23 @@ YOUTRACK_TOKEN = "perm:your-token-here"
|
|
|
278
281
|
Многие инструменты поддерживают опциональные параметры сохранения файлов для работы с большими датасетами:
|
|
279
282
|
|
|
280
283
|
- `saveToFile` — boolean, сохраняет результаты в JSON файл вместо возврата напрямую (полезно для больших датасетов)
|
|
281
|
-
- `filePath` — string, пользовательский путь к файлу (опционально, генерируется автоматически если не указан, директория создается при необходимости)
|
|
284
|
+
- `filePath` — string, пользовательский путь к файлу относительно `YOUTRACK_OUTPUT_DIR` (опционально, генерируется автоматически если не указан, директория создается при необходимости)
|
|
282
285
|
- `format` — string, формат вывода при сохранении в файл: `jsonl` (JSON Lines) или `json` (JSON массив). По умолчанию `jsonl`
|
|
283
286
|
- `overwrite` — boolean, разрешить перезапись существующих файлов при использовании явного пути к файлу. По умолчанию `false`
|
|
284
287
|
|
|
285
|
-
Когда `saveToFile` установлено в `true`, инструменты возвращают метаданные о
|
|
288
|
+
Когда `saveToFile` установлено в `true`, инструменты возвращают метаданные о сохранённом файле вместо полных данных. Все пути разрешаются относительно `YOUTRACK_OUTPUT_DIR`; абсолютные пути и сегменты `..` отвергаются.
|
|
289
|
+
|
|
290
|
+
### Пагинация
|
|
291
|
+
|
|
292
|
+
Большинство read-инструментов поддерживают `limit` (по умолчанию 100, максимум 200) и `skip` (по умолчанию 0); оба параметра прокидываются в YouTrack как `$top`/`$skip`. В ответах с агрегированным счётчиком используется поле `returned` (количество элементов на текущей странице) вместо `total`. Используйте последовательные значения `skip` для постраничного обхода больших выборок.
|
|
293
|
+
|
|
294
|
+
### Подтверждение деструктивных операций
|
|
295
|
+
|
|
296
|
+
Инструменты, удаляющие данные, требуют явного литерала `confirmation: true` во входных параметрах:
|
|
297
|
+
|
|
298
|
+
- `issue_attachment_delete`
|
|
299
|
+
- `issue_link_delete`
|
|
300
|
+
- `workitem_delete`
|
|
286
301
|
|
|
287
302
|
### Сервис
|
|
288
303
|
|
|
@@ -298,7 +313,7 @@ YOUTRACK_TOKEN = "perm:your-token-here"
|
|
|
298
313
|
| `issues_lookup` | Получить информацию о нескольких задачах YouTrack (пакетный режим, макс 50). **Использование:** Эффективное получение информации о множестве задач одновременно. **Возвращает:** те же поля, что и `issue_lookup`. **Ограничение:** максимум 50 задач за запрос | `issueIds[]` — массив кодов задач (например, `['PROJ-123', 'PROJ-124']`), макс 50; `briefOutput` — опционально (по умолчанию `true`) |
|
|
299
314
|
| `issue_details` | Режимы краткий/полный для деталей задачи. **Использование:** Просмотр деталей задачи. **Краткий (по умолчанию):** предопределённые поля — id, idReadable, summary, description, wikifiedDescription, usesMarkdown, created, updated, resolved, project, parent, assignee, reporter, updater, watchers(hasStar). **Полный (`briefOutput=false`):** добавляет `customFields(id,name,value(id,name,presentation),$type,possibleEvents(id,presentation))`, включая поле `State`. | `issueId` — код задачи; `briefOutput` — опционально (по умолчанию `true`); `saveToFile`, `filePath`, `format`, `overwrite` — параметры сохранения файлов |
|
|
300
315
|
| `issues_details` | Режимы краткий/полный для множества задач (макс 50). **Краткий:** предопределённые поля. **Полный (`briefOutput=false`):** добавляет `customFields` для каждой задачи. **Замечание:** объём ответа может быть большим, поэтому по умолчанию используется краткий режим. **Ограничение:** максимум 50 задач за запрос | `issueIds[]` — массив кодов задач, макс 50; `briefOutput` — опционально (по умолчанию `true`); `saveToFile`, `filePath`, `format`, `overwrite` — параметры сохранения файлов |
|
|
301
|
-
| `issue_comments` | Комментарии
|
|
316
|
+
| `issue_comments` | Комментарии задачи с серверной пагинацией. **Использование:** Просмотр всех комментариев к задаче. **Возвращает:** предопределенные поля - id, text, textPreview, usesMarkdown, author (id, login, name), created, updated, commentUrl (прямая ссылка на комментарий) | `issueId` — код задачи; `limit` (по умолчанию 100, макс 200), `skip` для пагинации; `saveToFile`, `filePath`, `format`, `overwrite` — параметры сохранения файлов |
|
|
302
317
|
| `issues_comments` | Комментарии для нескольких задач (пакетный режим, макс 50). **Использование:** Эффективное получение комментариев для множества задач одновременно. **Ограничение:** максимум 50 задач за запрос | `issueIds[]` — массив кодов задач, макс 50; `briefOutput` — опционально (по умолчанию `true`); `saveToFile`, `filePath`, `format`, `overwrite` — параметры сохранения файлов |
|
|
303
318
|
| `issue_create` | Создание новой задачи в YouTrack. **Использование:** Создание задач, подзадач (через `parentIssueId`), назначение исполнителя при создании, одновременное создание дополнительных связей. **Возвращает:** стандартные поля созданной задачи (id, idReadable, summary, description, wikifiedDescription, usesMarkdown, project, parent, assignee). Пользовательские поля не включены. | `projectId` — внутренний ID проекта, `summary`, опционально `description`, `parentIssueId`, `assigneeLogin`, `stateName`, `usesMarkdown`, `links[]` — массив объектов связи |
|
|
304
319
|
| `issue_update` | Обновление существующей задачи. **Использование:** Изменение summary, description, родительской задачи. **Примечание:** для изменения исполнителя используйте `issue_assign`. Пустая строка в `parentIssueId` удаляет родителя. **Возвращает:** стандартные поля обновленной задачи | `issueId` — ID или код задачи, опционально `summary`, `description`, `parentIssueId` (пустая строка очищает родителя), `usesMarkdown` |
|
|
@@ -312,10 +327,10 @@ YOUTRACK_TOKEN = "perm:your-token-here"
|
|
|
312
327
|
|
|
313
328
|
| Tool | Описание | Основные параметры |
|
|
314
329
|
| --- | --- | --- |
|
|
315
|
-
| `issue_links` | Список связей (линков) для
|
|
330
|
+
| `issue_links` | Список связей (линков) для задачи с серверной пагинацией. Возвращает id связи, направление (inward/outbound), тип связи и краткую информацию о связанной задаче | `issueId` — код задачи; `limit` (по умолчанию 100, макс 200), `skip` для пагинации |
|
|
316
331
|
| `issue_link_types` | Список доступных типов связей в YouTrack. Полезно для подбора корректного типа при создании связи | — |
|
|
317
332
|
| `issue_link_add` | Создание связи между двумя задачами | `sourceId` — исходная задача, `targetId` — целевая задача, `linkType` — имя или id типа связи (например, `Relates` или UUID), опционально `direction` (`outbound` или `inbound`) |
|
|
318
|
-
| `issue_link_delete` | Удаление существующей связи по её id
|
|
333
|
+
| `issue_link_delete` | Удаление существующей связи по её id (требует `confirmation: true`) | `issueId`, `linkId`, опционально `targetId`, `confirmation` — обязательно булев литерал `true` |
|
|
319
334
|
|
|
320
335
|
**issue_create — дополнительные параметры:**
|
|
321
336
|
|
|
@@ -348,14 +363,14 @@ YOUTRACK_TOKEN = "perm:your-token-here"
|
|
|
348
363
|
|
|
349
364
|
| Tool | Описание | Основные параметры |
|
|
350
365
|
| --- | --- | --- |
|
|
351
|
-
| `workitems_list` | Получение трудозатрат текущего или указанного
|
|
352
|
-
| `workitems_all_users` | Получение трудозатрат всех
|
|
353
|
-
| `workitems_for_users` | Получение трудозатрат выбранных
|
|
366
|
+
| `workitems_list` | Получение трудозатрат текущего или указанного пользователя с серверной пагинацией. **Использование:** Просмотр учтенного времени, фильтрация по задаче, автору, периоду | Опционально `issueId`, `author`, `startDate`, `endDate`, `allUsers`; `limit` (по умолчанию 100, макс 200), `skip` для пагинации; `saveToFile`, `filePath`, `format`, `overwrite` — параметры сохранения файлов |
|
|
367
|
+
| `workitems_all_users` | Получение трудозатрат всех пользователей с серверной пагинацией. **Возвращает:** те же поля, что и `workitems_list` | Опционально `issueId`, `startDate`, `endDate`; `limit` (по умолчанию 100, макс 200), `skip` для пагинации; `saveToFile`, `filePath`, `format`, `overwrite` — параметры сохранения файлов |
|
|
368
|
+
| `workitems_for_users` | Получение трудозатрат выбранных пользователей с per-user пагинацией. **Возвращает:** те же поля, что и `workitems_list` | `users[]` — массив логинов пользователей, опционально `issueId`, `startDate`, `endDate`; `limit` (по умолчанию 100, макс 200), `skip` применяются на каждого пользователя; `saveToFile`, `filePath`, `format`, `overwrite` — параметры сохранения файлов |
|
|
354
369
|
| `workitems_recent` | Получение последных записей трудозатрат с сортировкой по времени обновления (новые первыми). **Использование:** Быстрый просмотр недавних записей времени, мониторинг текущей активности. **Возвращает:** те же поля, что и `workitems_list`. **Ограничение:** по умолчанию 50 записей, макс 200 | Опционально `users[]` (по умолчанию текущий пользователь), `limit` (по умолчанию 50, макс 200); `saveToFile`, `filePath`, `format`, `overwrite` — параметры сохранения файлов |
|
|
355
370
|
| `workitem_create` | Создание записи трудозатрат. **Использование:** Учет времени на задачу за конкретную дату. **Возвращает:** предопределенные поля созданной записи. **Примечание:** может создавать дубликаты, для предотвращения используйте `workitem_create_idempotent` | `issueId` — ID задачи, `date` (формат YYYY-MM-DD, timestamp или date-time), `minutes` — количество минут (>0), опционально `summary` — краткий текст, `description`, `usesMarkdown` |
|
|
356
371
|
| `workitem_create_idempotent` | Создание записи без дублей (по описанию и дате). **Использование:** Безопасное создание записи времени с предотвращением дублирования. Проверяет существование похожей записи по описанию. **Возвращает:** предопределенные поля созданной или найденной записи | `issueId` — ID задачи, `date`, `minutes`, `description` — текст для поиска существующей записи, опционально `usesMarkdown` |
|
|
357
372
|
| `workitem_update` | Обновление записи трудозатрат. **Использование:** Исправление учтенного времени, даты или описания. **Возвращает:** предопределенные поля обновленной записи | `issueId` — ID задачи, `workItemId` — ID записи трудозатрат, опционально `date`, `minutes`, `summary` — новый текст, `description`, `usesMarkdown` |
|
|
358
|
-
| `workitem_delete` | Удаление записи
|
|
373
|
+
| `workitem_delete` | Удаление записи трудозатрат (требует `confirmation: true`). **Использование:** Удаление ошибочно созданных или неактуальных записей | `issueId`, `workItemId`, `confirmation` — обязательно булев литерал `true` |
|
|
359
374
|
| `workitems_create_period` | Массовое создание трудозатрат по диапазону дат. **Использование:** Учет регулярной работы за период, автоматическое исключение выходных и праздников. **Возвращает:** массив созданных записей с предопределенными полями. **Примечание:** автоматически пропускает выходные при `excludeWeekends=true` и даты из списка `holidays` | `issueId`, `startDate`, `endDate`, `minutes` — минут в день, опционально `summary`, `description`, `usesMarkdown`, `excludeWeekends` — исключить субботы и воскресенья, `excludeHolidays`, `holidays[]` — массив дат праздников (YYYY-MM-DD, timestamp или date-time), `preHolidays[]` — массив предпраздничных дат |
|
|
360
375
|
| `workitems_report_summary` | Сводный отчёт по трудозатратам. **Использование:** Анализ загрузки за период, сравнение плана и факта, выявление недостатка/избытка часов. **Возвращает:** общую статистику - общее количество минут, ожидаемое количество, отклонение, количество рабочих дней, данные по дням | Опционально `author` (логин), `issueId`, `startDate`, `endDate`, `expectedDailyMinutes` — норма минут в день (например, 480 для 8 часов), `excludeWeekends`, `excludeHolidays`, `holidays[]`, `preHolidays[]`, `allUsers` — включить всех пользователей; `saveToFile`, `filePath`, `format`, `overwrite` — параметры сохранения файлов |
|
|
361
376
|
| `workitems_report_invalid` | Список дней с отклонением от нормы времени. **Использование:** Поиск дней с недостатком или избытком учтенного времени, контроль соблюдения нормы. **Возвращает:** массив дней, где факт отличается от нормы, с детализацией по записям | Те же параметры, что и для `workitems_report_summary`; `saveToFile`, `filePath`, `format`, `overwrite` — параметры сохранения файлов |
|
|
@@ -366,10 +381,10 @@ YOUTRACK_TOKEN = "perm:your-token-here"
|
|
|
366
381
|
|
|
367
382
|
| Tool | Описание | Основные параметры |
|
|
368
383
|
| --- | --- | --- |
|
|
369
|
-
| `users_list` | Список
|
|
384
|
+
| `users_list` | Список пользователей YouTrack с серверной пагинацией. **Использование:** Просмотр доступных пользователей, поиск логинов для назначения задач. **Возвращает:** предопределенные поля - id, login, name, fullName, email | `limit` (по умолчанию 100, макс 200), `skip` для пагинации; `saveToFile`, `filePath`, `format`, `overwrite` — параметры сохранения файлов |
|
|
370
385
|
| `user_get` | Получение пользователя по логину. **Использование:** Проверка существования пользователя, получение деталей профиля. **Возвращает:** предопределенные поля - id, login, name, fullName, email | `login` — логин пользователя |
|
|
371
386
|
| `user_current` | Получение текущего аутентифицированного пользователя. **Использование:** Проверка, под каким пользователем работает API токен, получение собственного ID для операций. **Возвращает:** предопределенные поля - id, login, name, fullName, email | — |
|
|
372
|
-
| `projects_list` | Список
|
|
387
|
+
| `projects_list` | Список проектов YouTrack (по умолчанию авто-пагинация; для явной пагинации передайте `limit`/`skip`). **Возвращает:** предопределенные поля - id, shortName, name | Опционально `limit` (макс 200), `skip` |
|
|
373
388
|
| `project_get` | Получение проекта по короткому имени. **Использование:** Получение внутреннего ID проекта по короткому имени (например, `PROJ` → внутренний ID) для создания задач. **Возвращает:** предопределенные поля - id, shortName, name | `shortName` — короткое имя проекта (например, `PROJ`, `BC`) |
|
|
374
389
|
|
|
375
390
|
### Статьи
|
|
@@ -377,7 +392,7 @@ YOUTRACK_TOKEN = "perm:your-token-here"
|
|
|
377
392
|
| Tool | Описание | Основные параметры |
|
|
378
393
|
| --- | --- | --- |
|
|
379
394
|
| `article_get` | Получение статьи по ID. **Использование:** Чтение полного содержимого статьи базы знаний. **Возвращает:** предопределенные поля - id, idReadable, summary, content, contentPreview, usesMarkdown, parentArticle (id, idReadable), project (id, shortName, name) | `articleId` — ID статьи |
|
|
380
|
-
| `article_list` | Список статей с
|
|
395
|
+
| `article_list` | Список статей с фильтрами и серверной пагинацией. **Возвращает:** предопределенные поля - id, idReadable, summary, usesMarkdown, parentArticle (id, idReadable), project (id, shortName, name). Поле content не включено для производительности | Опционально `parentArticleId` — фильтр по родительской статье, `projectId` — фильтр по проекту; `limit` (по умолчанию 100, макс 200), `skip` для пагинации |
|
|
381
396
|
| `article_create` | Создание статьи в базе знаний. **Использование:** Создание документации, инструкций, статей поддержки. Поддерживает иерархическую структуру через `parentArticleId`. **Возвращает:** предопределенные поля созданной статьи - id, idReadable, summary, content, contentPreview, usesMarkdown, parentArticle, project | `summary` — заголовок статьи, опционально `content` — содержимое, `parentArticleId` — ID родительской статьи для создания подстатьи, `projectId` — ID проекта, `usesMarkdown` — использовать Markdown форматирование, `returnRendered` — вернуть отрендеренный preview |
|
|
382
397
|
| `article_update` | Обновление существующей статьи. **Использование:** Редактирование содержимого, изменение заголовка статьи. **Возвращает:** предопределенные поля обновленной статьи | `articleId` — ID статьи, опционально `summary` — новый заголовок, `content` — новое содержимое, `usesMarkdown`, `returnRendered` |
|
|
383
398
|
| `article_search` | Поиск статей в базе знаний по тексту. **Использование:** Полнотекстовый поиск по заголовкам и содержимому статей, поиск документации. Поддерживает фильтрацию по проекту и родительской статье. **Возвращает:** предопределенные поля - id, idReadable, summary, usesMarkdown, contentPreview (если `returnRendered=true`), parentArticle, project. Поле content не включено для производительности. **Ограничение:** минимум 2 символа в запросе, максимум 200 результатов | `query` — текст для поиска (минимум 2 символа), опционально `projectId` — фильтр по проекту, `parentArticleId` — фильтр по родительской статье, `limit` — максимум результатов (макс 200), `returnRendered` — вернуть отрендеренный preview содержимого; `saveToFile`, `filePath`, `format`, `overwrite` — параметры сохранения файлов |
|
|
@@ -400,4 +415,4 @@ YOUTRACK_TOKEN = "perm:your-token-here"
|
|
|
400
415
|
| `issue_attachment_get` | Получение детальной информации о конкретном вложении. **Использование:** Проверка метаданных файла (размер, имя, тип, автор, дата). **Возвращает:** подробные метаданные вложения | `issueId` — код задачи, `attachmentId` — ID вложения; `saveToFile`, `filePath`, `format`, `overwrite` — параметры сохранения файлов |
|
|
401
416
|
| `issue_attachment_download` | Получение URL для скачивания вложения или прямая загрузка файла в локальную файловую систему. **Использование:** Получение подписанного URL для загрузки файла или прямая загрузка файла в локальную файловую систему. **Возвращает:** метаданные вложения и подписанный URL, который можно использовать напрямую без дополнительной аутентификации, или информацию о загруженном файле | `issueId` — код задачи, `attachmentId` — ID вложения; `downloadToFile` — boolean, загрузить файл непосредственно в локальную файловую систему (по умолчанию: false); `downloadPath` — путь для сохранения файла (генерируется автоматически, если не указан); `overwrite` — разрешить перезапись существующих файлов (по умолчанию: false, выдает ошибку, если файл существует); `saveToFile`, `filePath`, `format`, `overwrite` — параметры сохранения файлов |
|
|
402
417
|
| `issue_attachment_upload` | Загрузка файлов к задаче YouTrack. **Использование:** Прикрепление документов, скриншотов, логов к задаче. Файлы должны существовать в локальной файловой системе. **Примечание:** можно прикреплять файлы только к существующим задачам, не при создании. **Ограничение:** максимум 10 файлов за запрос | `issueId` — код задачи, `filePaths[]` — массив абсолютных путей к файлам (макс 10), опционально `muteUpdateNotifications` — не отправлять уведомления об обновлении |
|
|
403
|
-
| `issue_attachment_delete` | Удаление вложения из задачи YouTrack
|
|
418
|
+
| `issue_attachment_delete` | Удаление вложения из задачи YouTrack (требует `confirmation: true`). **Использование:** Удаление устаревших или ошибочно загруженных файлов. Это деструктивная операция, которую нельзя отменить. | `issueId`, `attachmentId`, `confirmation` — обязательно булев литерал `true` |
|
package/README.md
CHANGED
|
@@ -42,6 +42,8 @@ MCP server for comprehensive YouTrack integration with the following capabilitie
|
|
|
42
42
|
- [Configuration for VS Code Cline](#configuration-for-vs-code-cline)
|
|
43
43
|
- [MCP Tools](#mcp-tools)
|
|
44
44
|
- [File Storage Parameters](#file-storage-parameters)
|
|
45
|
+
- [Pagination](#pagination)
|
|
46
|
+
- [Destructive Operation Confirmation](#destructive-operation-confirmation)
|
|
45
47
|
- [Service](#service)
|
|
46
48
|
- [Issues](#issues)
|
|
47
49
|
- [Issue Links](#issue-links)
|
|
@@ -67,6 +69,7 @@ MCP server for comprehensive YouTrack integration with the following capabilitie
|
|
|
67
69
|
- `YOUTRACK_PRE_HOLIDAYS` — optional comma-separated list of pre-holiday dates with reduced working hours
|
|
68
70
|
- `YOUTRACK_USER_ALIASES` — optional comma-separated list of `alias:login` mappings (e.g., `me:vyt,petya:p.petrov`), used for automatic assignee selection
|
|
69
71
|
- `YOUTRACK_DEFAULT_PROJECT` — optional project code used for manual verification tasks and default parent issues in docs/examples (use `PROJ` in documentation examples)
|
|
72
|
+
- `YOUTRACK_OUTPUT_DIR` — optional root directory for files produced by `saveToFile` and `downloadToFile`. Defaults to the current working directory. Absolute paths and parent-traversal segments (`..`) are rejected for safety
|
|
70
73
|
|
|
71
74
|
|
|
72
75
|
## Installation
|
|
@@ -283,11 +286,23 @@ To use this MCP server with [Cline](https://github.com/cline/cline) extension in
|
|
|
283
286
|
Many tools support optional file storage parameters for handling large datasets:
|
|
284
287
|
|
|
285
288
|
- `saveToFile` — boolean, saves results to a JSON file instead of returning directly (useful for large datasets)
|
|
286
|
-
- `filePath` — string, custom file path (optional, auto-generated if not provided, directory created if needed)
|
|
289
|
+
- `filePath` — string, custom file path relative to `YOUTRACK_OUTPUT_DIR` (optional, auto-generated if not provided, directory created if needed)
|
|
287
290
|
- `format` — string, output format when saving to file: `jsonl` (JSON Lines) or `json` (JSON array format). Default is `jsonl`
|
|
288
291
|
- `overwrite` — boolean, allow overwriting existing files when using explicit filePath. Default is `false`
|
|
289
292
|
|
|
290
|
-
When `saveToFile` is `true`, tools return metadata about the saved file instead of the full data.
|
|
293
|
+
When `saveToFile` is `true`, tools return metadata about the saved file instead of the full data. All paths are resolved against `YOUTRACK_OUTPUT_DIR`; absolute paths and `..` traversal segments are rejected.
|
|
294
|
+
|
|
295
|
+
### Pagination
|
|
296
|
+
|
|
297
|
+
Most read tools accept `limit` (default 100, max 200) and `skip` (default 0); both are forwarded to YouTrack as `$top`/`$skip`. Tools that return aggregated counts use `returned` (count of items in the current page) rather than `total`. Use successive `skip` values to walk through large result sets.
|
|
298
|
+
|
|
299
|
+
### Destructive Operation Confirmation
|
|
300
|
+
|
|
301
|
+
Tools that delete data require an explicit `confirmation: true` literal in their input:
|
|
302
|
+
|
|
303
|
+
- `issue_attachment_delete`
|
|
304
|
+
- `issue_link_delete`
|
|
305
|
+
- `workitem_delete`
|
|
291
306
|
|
|
292
307
|
### Service
|
|
293
308
|
|
|
@@ -303,7 +318,7 @@ When `saveToFile` is `true`, tools return metadata about the saved file instead
|
|
|
303
318
|
| `issues_lookup` | Get information about multiple YouTrack issues (batch mode, max 50). Note: Returns predefined fields including timestamps (created, updated) and basic info - id, idReadable, summary, description, wikifiedDescription, usesMarkdown, created, updated, project (id, shortName, name), parent (id, idReadable), assignee (id, login, name), reporter (id, login, name), updater (id, login, name). By default, custom fields are not included. Use briefOutput=false to get all customFields including State. | `issueIds[]` — array of issue codes (e.g., ['PROJ-123', 'PROJ-124']), max 50; `briefOutput` — optional boolean (default `true`) |
|
|
304
319
|
| `issue_details` | Issue details with brief/full modes | `issueId` — issue code; `briefOutput` — optional boolean (default `true`). Brief: predefined fields only. Full (`false`): adds `customFields` including `State`; `saveToFile`, `filePath`, `format`, `overwrite` — file storage options |
|
|
305
320
|
| `issues_details` | Detailed information about multiple issues (batch mode, max 50). Brief (default): predefined fields only. Full (`briefOutput=false`): adds `customFields` for each issue | `issueIds[]` — array of issue codes, max 50; `briefOutput` — optional boolean (default `true`); `saveToFile`, `filePath`, `format`, `overwrite` — file storage options |
|
|
306
|
-
| `issue_comments` | Issue comments | `issueId` — issue code; `saveToFile`, `filePath`, `format`, `overwrite` — file storage options |
|
|
321
|
+
| `issue_comments` | Issue comments with server-side pagination | `issueId` — issue code; `limit` (default 100, max 200), `skip` for pagination; `saveToFile`, `filePath`, `format`, `overwrite` — file storage options |
|
|
307
322
|
| `issues_comments` | Comments for multiple issues (batch mode, max 50) | `issueIds[]` — array of issue codes, max 50; `briefOutput` — optional boolean (default `true`); `saveToFile`, `filePath`, `format`, `overwrite` — file storage options |
|
|
308
323
|
| `issue_create` | Create issue | `projectId`, `summary`, optional `description`, `parentIssueId`, `assigneeLogin`, `stateName`, `usesMarkdown`, `links` (array of link objects) |
|
|
309
324
|
| `issue_update` | Update existing issue | `issueId`, optionally `summary`, `description`, `parentIssueId` (empty string clears parent), `usesMarkdown` |
|
|
@@ -316,16 +331,16 @@ When `saveToFile` is `true`, tools return metadata about the saved file instead
|
|
|
316
331
|
| `issue_attachment_get` | Get attachment info | `issueId`, `attachmentId`; `saveToFile`, `filePath`, `format`, `overwrite` — file storage options |
|
|
317
332
|
| `issue_attachment_download` | Get download URL for attachment or download file directly to local filesystem | `issueId`, `attachmentId` — returns signed download URL when downloadToFile=false; `downloadToFile` — boolean, download file directly to local system (default: false); `downloadPath` — custom path to save file (auto-generated if not provided); `overwrite` — allow overwriting existing files (default: false, throws error if file exists); `saveToFile`, `filePath`, `format`, `overwrite` — file storage options for metadata |
|
|
318
333
|
| `issue_attachment_upload` | Upload files to issue | `issueId`, `filePaths[]` — array of file paths (max 10), optionally `muteUpdateNotifications` |
|
|
319
|
-
| `issue_attachment_delete` | Delete attachment (requires confirmation) | `issueId`, `attachmentId`, `confirmation` (must be `true`) |
|
|
334
|
+
| `issue_attachment_delete` | Delete attachment (requires `confirmation: true`) | `issueId`, `attachmentId`, `confirmation` (must be the boolean literal `true`) |
|
|
320
335
|
|
|
321
336
|
### Issue Links
|
|
322
337
|
|
|
323
338
|
| Tool | Description | Main Parameters |
|
|
324
339
|
| --- | --- | --- |
|
|
325
|
-
| `issue_links` | List links for an issue
|
|
326
|
-
| `issue_link_types` | List available link types | — |
|
|
340
|
+
| `issue_links` | List links for an issue with server-side pagination. Returns link id, direction, linkType, and counterpart issue brief | `issueId` — issue code; `limit` (default 100, max 200), `skip` for pagination |
|
|
341
|
+
| `issue_link_types` | List available link types (cached single-flight) | — |
|
|
327
342
|
| `issue_link_add` | Create a link between two issues | `sourceId`, `targetId`, `linkType` (name or id), optionally `direction` (`outbound` or `inbound`) |
|
|
328
|
-
| `issue_link_delete` | Delete a link by id for a specific issue | `issueId`
|
|
343
|
+
| `issue_link_delete` | Delete a link by id for a specific issue (requires `confirmation: true`) | `issueId`, `linkId`, optionally `targetId`, `confirmation` (must be the boolean literal `true`) |
|
|
329
344
|
|
|
330
345
|
### Issues Status
|
|
331
346
|
|
|
@@ -348,14 +363,14 @@ When `saveToFile` is `true`, tools return metadata about the saved file instead
|
|
|
348
363
|
|
|
349
364
|
| Tool | Description | Main Parameters |
|
|
350
365
|
| --- | --- | --- |
|
|
351
|
-
| `workitems_list` | Get work items for current or specified user | Optionally `issueId`, `author`, `startDate`, `endDate`, `allUsers`; `saveToFile`, `filePath`, `format`, `overwrite` — file storage options |
|
|
352
|
-
| `workitems_all_users` | Get work items for all users | Optionally `issueId`, `startDate`, `endDate`; `saveToFile`, `filePath`, `format`, `overwrite` — file storage options |
|
|
353
|
-
| `workitems_for_users` | Get work items for selected users | `users[]`, optionally `issueId`, `startDate`, `endDate`; `saveToFile`, `filePath`, `format`, `overwrite` — file storage options |
|
|
366
|
+
| `workitems_list` | Get work items for current or specified user with server-side pagination | Optionally `issueId`, `author`, `startDate`, `endDate`, `allUsers`; `limit` (default 100, max 200), `skip` for pagination; `saveToFile`, `filePath`, `format`, `overwrite` — file storage options |
|
|
367
|
+
| `workitems_all_users` | Get work items for all users with server-side pagination | Optionally `issueId`, `startDate`, `endDate`; `limit` (default 100, max 200), `skip` for pagination; `saveToFile`, `filePath`, `format`, `overwrite` — file storage options |
|
|
368
|
+
| `workitems_for_users` | Get work items for selected users with per-user pagination | `users[]`, optionally `issueId`, `startDate`, `endDate`; `limit` (default 100, max 200), `skip` applied per user; `saveToFile`, `filePath`, `format`, `overwrite` — file storage options |
|
|
354
369
|
| `workitems_recent` | Get recent work items sorted by update time (newest first) | Optionally `users[]` (defaults to current user), `limit` (default 50, max 200); `saveToFile`, `filePath`, `format`, `overwrite` — file storage options |
|
|
355
370
|
| `workitem_create` | Create work item entry | `issueId`, `date`, `minutes`, optionally `summary`, `description`, `usesMarkdown` |
|
|
356
371
|
| `workitem_create_idempotent` | Create work item without duplicates (by description and date) | `issueId`, `date`, `minutes`, `description`, optionally `usesMarkdown` |
|
|
357
372
|
| `workitem_update` | Update work item (recreate) | `issueId`, `workItemId`, optionally `date`, `minutes`, `summary`, `description`, `usesMarkdown` |
|
|
358
|
-
| `workitem_delete` | Delete work item | `issueId`, `workItemId` |
|
|
373
|
+
| `workitem_delete` | Delete work item (requires `confirmation: true`) | `issueId`, `workItemId`, `confirmation` (must be the boolean literal `true`) |
|
|
359
374
|
| `workitems_create_period` | Batch create for date range | `issueId`, `startDate`, `endDate`, `minutes`, optionally `summary`, `description`, `usesMarkdown`, `excludeWeekends`, `excludeHolidays`, `holidays[]`, `preHolidays[]` |
|
|
360
375
|
| `workitems_report_summary` | Summary report for work items | Common parameters: `author`, `issueId`, `startDate`, `endDate`, `expectedDailyMinutes`, `excludeWeekends`, `excludeHolidays`, `holidays[]`, `preHolidays[]`, `allUsers`; `saveToFile`, `filePath`, `format`, `overwrite` — file storage options |
|
|
361
376
|
| `workitems_report_invalid` | Days with deviation from expected hours | Same parameters as summary; `saveToFile`, `filePath`, `format`, `overwrite` — file storage options |
|
|
@@ -366,10 +381,10 @@ When `saveToFile` is `true`, tools return metadata about the saved file instead
|
|
|
366
381
|
|
|
367
382
|
| Tool | Description | Main Parameters |
|
|
368
383
|
| --- | --- | --- |
|
|
369
|
-
| `users_list` | List all YouTrack users | `saveToFile`, `filePath`, `format`, `overwrite` — file storage options |
|
|
384
|
+
| `users_list` | List all YouTrack users with server-side pagination | `limit` (default 100, max 200), `skip` for pagination; `saveToFile`, `filePath`, `format`, `overwrite` — file storage options |
|
|
370
385
|
| `user_get` | Get user by login | `login` — user login |
|
|
371
386
|
| `user_current` | Get current authenticated user | — |
|
|
372
|
-
| `projects_list` | List all YouTrack projects |
|
|
387
|
+
| `projects_list` | List all YouTrack projects (auto-paginated by default; pass `limit`/`skip` for explicit paging) | Optional `limit` (max 200), `skip` |
|
|
373
388
|
| `project_get` | Get project by short name | `shortName` — project short name |
|
|
374
389
|
|
|
375
390
|
### Articles
|
|
@@ -377,7 +392,7 @@ When `saveToFile` is `true`, tools return metadata about the saved file instead
|
|
|
377
392
|
| Tool | Description | Main Parameters |
|
|
378
393
|
| --- | --- | --- |
|
|
379
394
|
| `article_get` | Get article by ID | `articleId` |
|
|
380
|
-
| `article_list` | List articles with filters | Optionally `parentArticleId`, `projectId` |
|
|
395
|
+
| `article_list` | List articles with filters and server-side pagination | Optionally `parentArticleId`, `projectId`; `limit` (default 100, max 200), `skip` for pagination |
|
|
381
396
|
| `article_create` | Create article in knowledge base | `summary`, optionally `content`, `parentArticleId`, `projectId`, `usesMarkdown`, `returnRendered` |
|
|
382
397
|
| `article_update` | Update article | `articleId`, optionally `summary`, `content`, `usesMarkdown`, `returnRendered` |
|
|
383
398
|
| `article_search` | Search articles in knowledge base | `query`, optionally `projectId`, `parentArticleId`, `limit`, `returnRendered`; `saveToFile`, `filePath`, `format`, `overwrite` — file storage options |
|
|
@@ -403,9 +418,11 @@ When `saveToFile` is `true`, tools return metadata about the saved file instead
|
|
|
403
418
|
|
|
404
419
|
### Destructive Operations
|
|
405
420
|
|
|
406
|
-
Some operations cannot be undone and require explicit confirmation:
|
|
421
|
+
Some operations cannot be undone and require an explicit `confirmation: true` literal in their input:
|
|
407
422
|
|
|
408
|
-
- **`issue_attachment_delete`**
|
|
423
|
+
- **`issue_attachment_delete`** — deleted attachments cannot be recovered.
|
|
424
|
+
- **`issue_link_delete`** — re-fetch `issue_links` afterwards to verify the relationship was removed.
|
|
425
|
+
- **`workitem_delete`** — work item entries are permanently lost; re-fetch `workitems_list` to confirm.
|
|
409
426
|
**issue_create parameters:**
|
|
410
427
|
|
|
411
428
|
- `parentIssueId` — resolves against `YOUTRACK_DEFAULT_PROJECT` when no project prefix is provided (e.g., `123` → `BC-123` if default project is `BC`).
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitalyostanin/youtrack-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"build": "tsc -p tsconfig.json",
|
|
6
6
|
"postbuild": "chmod +x dist/index.js",
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
"dev:watch": "tsc -p tsconfig.json --watch",
|
|
10
10
|
"lint": "eslint .",
|
|
11
11
|
"lint:fix": "eslint . --fix",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"test:watch": "vitest",
|
|
14
|
+
"test:coverage": "vitest run --coverage",
|
|
12
15
|
"prepublishOnly": "npm run build"
|
|
13
16
|
},
|
|
14
17
|
"keywords": [
|
|
@@ -55,6 +58,7 @@
|
|
|
55
58
|
"axios": "^1.12.2",
|
|
56
59
|
"form-data": "^4.0.1",
|
|
57
60
|
"luxon": "^3.5.0",
|
|
61
|
+
"stream-json": "^2.1.0",
|
|
58
62
|
"zod": "^3.25.76"
|
|
59
63
|
},
|
|
60
64
|
"devDependencies": {
|
|
@@ -62,9 +66,12 @@
|
|
|
62
66
|
"@types/luxon": "^3.7.1",
|
|
63
67
|
"@types/node": "^22.18.10",
|
|
64
68
|
"@vitalyostanin/eslint-prefer-de-morgan-law": "^1.0.2",
|
|
69
|
+
"@vitest/coverage-v8": "^4.1.5",
|
|
65
70
|
"eslint": "^9.37.0",
|
|
71
|
+
"nock": "^14.0.14",
|
|
66
72
|
"typescript": "^5.6.2",
|
|
67
73
|
"typescript-eslint": "^8.46.0",
|
|
74
|
+
"vitest": "^4.1.5",
|
|
68
75
|
"zod-to-json-schema": "^3.24.6"
|
|
69
76
|
}
|
|
70
77
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { mkdtempSync, rmSync, existsSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { loadConfig, enrichConfigWithRedaction } from "../config.js";
|
|
6
|
+
describe("loadConfig", () => {
|
|
7
|
+
let tmp;
|
|
8
|
+
const oldEnv = { ...process.env };
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
tmp = mkdtempSync(join(tmpdir(), "yt-cfg-"));
|
|
11
|
+
process.env = {
|
|
12
|
+
YOUTRACK_URL: "https://yt.example.com",
|
|
13
|
+
YOUTRACK_TOKEN: "perm:test-token",
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
18
|
+
process.env = { ...oldEnv };
|
|
19
|
+
});
|
|
20
|
+
it("uses cwd by default for outputDir", () => {
|
|
21
|
+
const cfg = loadConfig();
|
|
22
|
+
expect(cfg.outputDir).toBe(resolve(process.cwd()));
|
|
23
|
+
});
|
|
24
|
+
it("honors YOUTRACK_OUTPUT_DIR", () => {
|
|
25
|
+
process.env.YOUTRACK_OUTPUT_DIR = tmp;
|
|
26
|
+
const cfg = loadConfig();
|
|
27
|
+
expect(cfg.outputDir).toBe(resolve(tmp));
|
|
28
|
+
});
|
|
29
|
+
it("creates outputDir if missing", () => {
|
|
30
|
+
const sub = join(tmp, "missing-subdir");
|
|
31
|
+
process.env.YOUTRACK_OUTPUT_DIR = sub;
|
|
32
|
+
loadConfig();
|
|
33
|
+
expect(existsSync(sub)).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
it("throws when YOUTRACK_TOKEN is missing", () => {
|
|
36
|
+
delete process.env.YOUTRACK_TOKEN;
|
|
37
|
+
expect(() => loadConfig()).toThrow();
|
|
38
|
+
});
|
|
39
|
+
it("redacts token via enrichConfigWithRedaction", () => {
|
|
40
|
+
const cfg = loadConfig();
|
|
41
|
+
const redacted = enrichConfigWithRedaction(cfg);
|
|
42
|
+
expect(redacted.hasToken).toBe(true);
|
|
43
|
+
expect(redacted.token).toBeUndefined();
|
|
44
|
+
expect(redacted.outputDir).toBe(cfg.outputDir);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
//# sourceMappingURL=config.test.js.map
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { YoutrackClient } from "../youtrack-client.js";
|
|
3
|
+
const config = {
|
|
4
|
+
baseUrl: "https://yt.test",
|
|
5
|
+
token: "perm:test",
|
|
6
|
+
defaultProject: "BC",
|
|
7
|
+
outputDir: "/tmp",
|
|
8
|
+
timezone: "UTC",
|
|
9
|
+
};
|
|
10
|
+
function getHttp(client) {
|
|
11
|
+
return client.http;
|
|
12
|
+
}
|
|
13
|
+
function methodNotAllowed() {
|
|
14
|
+
return {
|
|
15
|
+
isAxiosError: true,
|
|
16
|
+
message: "Request failed with status code 405",
|
|
17
|
+
response: { status: 405, data: { error: "method_not_allowed" } },
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
describe("deleteIssueLink subtask fallback", () => {
|
|
21
|
+
it("uses linkToDelete.issue.idReadable when targetId is missing", async () => {
|
|
22
|
+
const client = new YoutrackClient(config);
|
|
23
|
+
const http = getHttp(client);
|
|
24
|
+
vi.spyOn(http, "delete").mockRejectedValue(methodNotAllowed());
|
|
25
|
+
vi.spyOn(client, "getIssueLinks").mockResolvedValue({
|
|
26
|
+
issueId: "BC-1",
|
|
27
|
+
links: [
|
|
28
|
+
{
|
|
29
|
+
id: "L1",
|
|
30
|
+
direction: "OUTWARD",
|
|
31
|
+
linkType: { name: "Subtask", id: "lt-sub" },
|
|
32
|
+
issue: { idReadable: "BC-99", id: "1-99", summary: "s" },
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
});
|
|
36
|
+
let captured;
|
|
37
|
+
vi.spyOn(http, "post").mockImplementation((...args) => {
|
|
38
|
+
captured = { url: String(args[0]), body: args[1] };
|
|
39
|
+
return Promise.resolve({ data: {} });
|
|
40
|
+
});
|
|
41
|
+
await client.deleteIssueLink({ issueId: "BC-1", linkId: "L1" });
|
|
42
|
+
expect(captured?.url).toBe("/api/commands");
|
|
43
|
+
expect(captured?.body.query).toBe("remove subtask of BC-99");
|
|
44
|
+
const issues = captured?.body.issues;
|
|
45
|
+
expect(issues[0].idReadable).toBe("BC-1");
|
|
46
|
+
});
|
|
47
|
+
it("uses provided targetId when given (regular link)", async () => {
|
|
48
|
+
const client = new YoutrackClient(config);
|
|
49
|
+
const http = getHttp(client);
|
|
50
|
+
vi.spyOn(http, "delete").mockRejectedValue(methodNotAllowed());
|
|
51
|
+
vi.spyOn(client, "getIssueLinks").mockResolvedValue({
|
|
52
|
+
issueId: "BC-1",
|
|
53
|
+
links: [
|
|
54
|
+
{
|
|
55
|
+
id: "L1",
|
|
56
|
+
direction: "OUTWARD",
|
|
57
|
+
linkType: {
|
|
58
|
+
name: "Relates",
|
|
59
|
+
id: "lt-rel",
|
|
60
|
+
outwardName: "relates to",
|
|
61
|
+
inwardName: "relates to",
|
|
62
|
+
},
|
|
63
|
+
issue: { idReadable: "BC-99", id: "1-99", summary: "s" },
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
});
|
|
67
|
+
let captured;
|
|
68
|
+
vi.spyOn(http, "post").mockImplementation((...args) => {
|
|
69
|
+
captured = { body: args[1] };
|
|
70
|
+
return Promise.resolve({ data: {} });
|
|
71
|
+
});
|
|
72
|
+
await client.deleteIssueLink({ issueId: "BC-1", linkId: "L1", targetId: "BC-7" });
|
|
73
|
+
expect(String(captured?.body.query)).toContain("BC-7");
|
|
74
|
+
});
|
|
75
|
+
it("throws when neither input.targetId nor link payload provide a target", async () => {
|
|
76
|
+
const client = new YoutrackClient(config);
|
|
77
|
+
const http = getHttp(client);
|
|
78
|
+
vi.spyOn(http, "delete").mockRejectedValue(methodNotAllowed());
|
|
79
|
+
vi.spyOn(client, "getIssueLinks").mockResolvedValue({
|
|
80
|
+
issueId: "BC-1",
|
|
81
|
+
links: [
|
|
82
|
+
{
|
|
83
|
+
id: "L1",
|
|
84
|
+
direction: "OUTWARD",
|
|
85
|
+
linkType: { name: "Subtask", id: "lt-sub" },
|
|
86
|
+
issue: { idReadable: "", id: "1-99", summary: "s" },
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
});
|
|
90
|
+
await expect(client.deleteIssueLink({ issueId: "BC-1", linkId: "L1" })).rejects.toThrow(/Cannot determine target issue id/);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
//# sourceMappingURL=youtrack-client.delete-link.test.js.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { YoutrackClient } from "../youtrack-client.js";
|
|
3
|
+
const config = {
|
|
4
|
+
baseUrl: "https://yt.test",
|
|
5
|
+
token: "perm:test",
|
|
6
|
+
defaultProject: "BC",
|
|
7
|
+
outputDir: "/tmp",
|
|
8
|
+
timezone: "UTC",
|
|
9
|
+
};
|
|
10
|
+
function getHttp(client) {
|
|
11
|
+
return client.http;
|
|
12
|
+
}
|
|
13
|
+
function makeAxiosError(status, data) {
|
|
14
|
+
return {
|
|
15
|
+
isAxiosError: true,
|
|
16
|
+
message: `Request failed with status code ${status}`,
|
|
17
|
+
response: { status, data },
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
describe("normalizeError details whitelist", () => {
|
|
21
|
+
it("strips unknown keys (stack, pii) from response.data", async () => {
|
|
22
|
+
const client = new YoutrackClient(config);
|
|
23
|
+
vi.spyOn(getHttp(client), "get").mockRejectedValue(makeAxiosError(400, {
|
|
24
|
+
error: "bad_request",
|
|
25
|
+
error_description: "issue not found",
|
|
26
|
+
stack: "internal: org.youtrack...",
|
|
27
|
+
pii: "user@example.com",
|
|
28
|
+
}));
|
|
29
|
+
let captured;
|
|
30
|
+
try {
|
|
31
|
+
await client.getIssue("BC-1");
|
|
32
|
+
throw new Error("expected failure");
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
captured = err;
|
|
36
|
+
}
|
|
37
|
+
expect(captured?.status).toBe(400);
|
|
38
|
+
expect(captured?.details).toEqual({
|
|
39
|
+
error: "bad_request",
|
|
40
|
+
error_description: "issue not found",
|
|
41
|
+
});
|
|
42
|
+
expect(captured?.details).not.toHaveProperty("stack");
|
|
43
|
+
expect(captured?.details).not.toHaveProperty("pii");
|
|
44
|
+
});
|
|
45
|
+
it("returns undefined details when nothing in the whitelist matches", async () => {
|
|
46
|
+
const client = new YoutrackClient(config);
|
|
47
|
+
vi.spyOn(getHttp(client), "get").mockRejectedValue(makeAxiosError(500, { stack: "x", trace: "y" }));
|
|
48
|
+
try {
|
|
49
|
+
await client.getIssue("BC-1");
|
|
50
|
+
throw new Error("expected failure");
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
const e = err;
|
|
54
|
+
expect(e.status).toBe(500);
|
|
55
|
+
expect(e.details).toBeUndefined();
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
it("keeps numeric code and message strings", async () => {
|
|
59
|
+
const client = new YoutrackClient(config);
|
|
60
|
+
vi.spyOn(getHttp(client), "get").mockRejectedValue(makeAxiosError(403, { code: 403, message: "forbidden", debug: { ip: "1.2.3.4" } }));
|
|
61
|
+
try {
|
|
62
|
+
await client.getIssue("BC-1");
|
|
63
|
+
throw new Error("expected failure");
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
const e = err;
|
|
67
|
+
expect(e.details).toEqual({ code: 403, message: "forbidden" });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=youtrack-client.normalize-error.test.js.map
|